SPI Peripheral in S32K144 MCU
So hello guys, welcome back to NXP Semiconductors S32K144 MCU Tutorial series. In the last 2 blogs we had started with S32K144 MCU GPIO Peripheral & UART Peripheral .
Table of Contents
In this blog we are going to explore the SPI Peripheral. Going to Start with SPI peripheral. Objective would be to get.
- familiarity with SPI peripheral for S32K144 MCU.
- Would be understanding SPI peripheral from Hardware point of view in S32K144 MCU.
- Going to understand then how to use SPI peripheral via S32K SDK/lpspi driver.
- Would also be demonstrating the spi_echo_pall sketch.
So read along the blog and do tell me its reviews
SPI Peripheral Theory
Serial Peripjeral interface is a synchronous serial communication interface used in embedded systems, typically to perform short distance communications between microcontrollers and device. Typical applications include interfacing to LCD displays, memory cards, Secure Digital cards and etc.
SPI Peripheral in S32K144 MCU
In S32K144 MCU, SPI protocol can be used via 2 peripherals: LPSPI & FlexIO.
LPSPI is referred as Low Power Serial Peripheral Interface. LPSPI is on chip peripheral only to do SPI communication protocol. SPI is a serial protocol which is done via SPI supported peripherals in the Microcontrollers.
Also, in S32K144 there is FlexIO peripheral through which on-board serial communication protocols like UART, I2C & SPI can be emulated. So through FlexIO peripheral, also SPI peripheral can be implemented. To know about FlexIO peripheral in S32K144, refer to this blog.
Features of SPI via LPSPI peripheral in S32K144 MCU:
- LPSPI module supports efficient interface to an SPI bus, as a master and slave.
- LPSPI is designed to use little CPU overhead, with DMA support. LPSPI can generate a DMA request.
- SPI devices communicate in full duplex mode using a master-slave scheme, with a single master at a time.
- Single master can control multiple slave devices using individual slave select (SS) lines.
- If MCU is configured as Master, then it will generate the frame for reading and wiriting and SPI clock which is synchronous.
- Supports daisychain for controlling multiple slave sharing the same chip select.
- Configurable clock polarity and clock phase
- Master operation supporting upto 4 peripheral chip selects at a time
- Transmit and receive FIFO of 4 words for both master and slave device.
- Flexible timing parameters in master mode, including SCK frequency and delays between PCS and SCK edges.
- Support for Full duplex transfers, supporting 1 bit transfers and receive on each clock edge.
- Support for full-duplex transfers, supporting 1-bit/2-bit/4-bit transfers and receive on each clock edge.
How to get started with I2C peripheral in S32K144 MCU
SPI Hardware Pinout in S32K144 MCU
LPSPI Pinout and Hardware Instances
LPSPI peripheral in S32K144 has 3 instances: LPSPI0, LPSPI1, LPSPI2.
In S32K MCU, LPSPI peripheral can be used in serial and parallel data transfers. For this blog we are going to focus on serial data transfers. to know about parallel data transfers, refer to this blog.
All the LPSPI instances has following pins, for using them.
- SCK (Serial clock): This pin is used to generate the clock pulses in SPI communication by the Master.
- SOUT (Serial data out): This pin is MOSI pin.
- SIN (Serial Data Input): This pin is MISO pin.
- PCS [0] (Peripheral Chip Select 0): This pin is used to select the slave in SPI communication. Master device will generate a Low Signal on this Pin to select the Slave. And generate High signal to deselect the Slave.
- PCS [1]: Peripheral Chip select 1.
- PCS [2]: Peripheral Chip Select 2.
- PCS [3]:Peripheral Chip Select 3
Each LPSPI instance in S32K144 supports all the above-mentioned pins, with below mentioned pin details. Refer to this blog to know about pins signal description in S32K144 MCU
In LPSPI0 there are following number of pins:
- For PCS0 there are 2 pins
- For PCS1 there is 1 pin.
- For PCS2 there is 1 pin.
- For PCS3 there is 1 pin.
- For SCK there are 3 pins
- For SIN there are 3 pins.
- For SOUT there are 3 pins3 pins.
In LPSPI1 there are following number of pins:
- For PCS0 there are 2 pins
- For PCS1 there is 1 pin.
- For PCS2 there is 1 pin.
- For PCS3 there is 1 pin.
- For SCK there are 2 pins
- For SIN there are 2 pins.
- For SOUT there are 3 pins.
In LPSPI2 there are following number of pins:
- For PCS0 there are 3 pins
- For PCS1 there is 1 pin.
- For PCS2 there is 1 pin.
- For PCS3 there is 1 pin.
- For SCK there are 2 pins
- For SIN there are 2 pins.
- For SOUT there are 2 pins.
How to do LPSPI Pin Configuration
In a MCU a single pin can work as multiple function, so we have to configure that which function we need, accordingly pins have to be configured. This configuration of Alternate functions of pins in S32K144 MCU is done by Signal Multiplexing peripheral. One can configure which pin to use for LPI2C, via Signal Multiplexing peripheral, in which there is a register Pin Control Register (PCR) which has Pin Mux Control bits(MUX) for configuring the alternate functions of the pins.
For example, we are using LPISPI0. Now in LPISPI0 for using LPSPI0_PCS0, LPSPI0_SCK, LPSPI0_SOUT, LPSPI0_SIN pins one can configure PTB0, PTB2, PTB4, PTE1 pins:
- You can see SSS column in the excel in that for PTB0 under LPSPI0_PCS has value of 0000_0011. Last 3 bits of this value represents the MUX values to be configured for configuring PTB0 pin as LPSPI0_PCS pin, in PORT_PCRn register.
- You can see SSS column in the excel in that for PTB2 under LPSPI0_SCK has value of 0000_0011. Last 3 bits of this value represents the MUX values to be configured for configuring PTB2 pin as LPSPI0_SCK pin, in PORT_PCRn register.
- You can see SSS column in the excel in that for PTB4 under LPSPI0_SOUT has value of 0000_0011. Last 3 bits of this value represents the MUX values to be configured for configuring PTB4 pin as LPSPI0_SOUT pin, in PORT_PCRn register.
- You can see SSS column in the excel in that for PTE1 under LPSPI0_SCK has value of 0000_0010. Last 3 bits of this value represents the MUX values to be configured for configuring PTE1 pin as LPSPI0_SIN pin, in PORT_PCRn register.
This part of LPSPI pins configuration is done internally by S32 SDK/pin driver (Its detail overview is in GPIO Peripheral in S32K144 MCU). When writing the code, we just need to configure the structure g_pin_InitConfig in which. mux member for the corresponding MCU pin will be assigned value according to last 3 bits of SSS column, as shown below and pass that structure in PINS_DRV_Init().
/*! @brief Definitions/Declarations for BOARD_InitPins Functional Group */
/*! @brief User number of configured pins */
#define NUM_OF_CONFIGURED_PINS0 4U
/*! @brief User configuration structure */
pin_settings_config_t g_pin_mux_InitConfigArr0[NUM_OF_CONFIGURED_PINS0] = {
{
.base = PORTB,
.pinPortIdx = 0U,
.pullConfig = PORT_INTERNAL_PULL_NOT_ENABLED,
.driveSelect = PORT_LOW_DRIVE_STRENGTH,
.passiveFilter = false,
.mux = PORT_MUX_ALT3,
.pinLock = false,
.intConfig = PORT_DMA_INT_DISABLED,
.clearIntFlag = false,
.gpioBase = NULL,
.digitalFilter = false,
},
{
.base = PORTB,
.pinPortIdx = 2U,
.pullConfig = PORT_INTERNAL_PULL_NOT_ENABLED,
.driveSelect = PORT_LOW_DRIVE_STRENGTH,
.passiveFilter = false,
.mux = PORT_MUX_ALT3,
.pinLock = false,
.intConfig = PORT_DMA_INT_DISABLED,
.clearIntFlag = false,
.gpioBase = NULL,
.digitalFilter = false,
},
{
.base = PORTB,
.pinPortIdx = 4U,
.pullConfig = PORT_INTERNAL_PULL_NOT_ENABLED,
.driveSelect = PORT_LOW_DRIVE_STRENGTH,
.passiveFilter = false,
.mux = PORT_MUX_ALT3,
.pinLock = false,
.intConfig = PORT_DMA_INT_DISABLED,
.clearIntFlag = false,
.gpioBase = NULL,
.digitalFilter = false,
},
{
.base = PORTE,
.pinPortIdx = 1U,
.pullConfig = PORT_INTERNAL_PULL_NOT_ENABLED,
.driveSelect = PORT_LOW_DRIVE_STRENGTH,
.passiveFilter = false,
.mux = PORT_MUX_ALT2,
.pinLock = false,
.intConfig = PORT_DMA_INT_DISABLED,
.clearIntFlag = false,
.gpioBase = NULL,
.digitalFilter = false,
},
};
PINS_DRV_Init(NUM_OF_CONFIGURED_PINS0, g_pin_mux_InitConfigArr0);
At line 12, 25, 38, 51 you see .mux is assigned with PORT_MUX_ALT_3 & PORT_MUX_ALT_2. The value of this is taken from port_mux_t Enum which is defined in pins_driver.h file as follows. The members defined in this Enum is according to the MUX bits values defined in PCRn register. So according to the value of the last 3 bits of SSS column, we will configure the. mux member of g_pin_InitConfig structure
/*!
* @brief Configures the Pin mux selection
* Implements : port_mux_t_Class
*/
typedef enum
{
PORT_PIN_DISABLED = 0U, /*0b000!< corresponding pin is disabled, but is used as an analog pin */
PORT_MUX_AS_GPIO = 1U, /*0b001!< corresponding pin is configured as GPIO */
PORT_MUX_ALT2 = 2U, /*0b010!< chip-specific */
PORT_MUX_ALT3 = 3U, /*0b011!< chip-specific */
PORT_MUX_ALT4 = 4U, /*0b100!< chip-specific */
PORT_MUX_ALT5 = 5U, /*0b101!< chip-specific */
PORT_MUX_ALT6 = 6U, /*0b110!< chip-specific */
PORT_MUX_ALT7 = 7U, /*0b111!< chip-specific */
#if FEATURE_PINS_HAS_ADC_INTERLEAVE_EN
PORT_MUX_ADC_INTERLEAVE = 8U /*!< when selected, ADC Interleaved channel is connected to current pin
* and disconnected to opposed pin
* ADC1_SE14-PTB15 | ADC1_SE15-PTB16 | ADC0_SE8-PTC0 | ADC0_SE9-PTC1
* ADC1_SE14-PTB0 | ADC1_SE15-PTB1 | ADC0_SE8-PTB13 | ADC0_SE9-PTB14 */
#endif /* FEATURE_PINS_HAS_ADC_INTERLEAVE_EN */
} port_mux_t;
SPI SDK in S32K144 MCU
LPSPI SDK
S32K SDK/drivers provide an easy to use and quick way to use the LPI2C peripheral in S32K144, which is known as LPSPI SDK.
Each S32 SDK driver can be configured and enabled to use in the project via S32 Configuration Tool. Will be digging into that part, in next section. For now, let’s understand the LPSPI SDK in some detail, so as to use SPI peripheral via LPSPI.
n the SDK of LPSPI there are header and source files for LPSPI Driver and LPSPI Interrupt
- LPSPI interrupt files contains functions for using &configuring of LPSPI interrupts and some common functions that has to be used by both master and slave
- LPSPI driver files contains functions for using/configuration of LPSPI Peripheral.
LPSPI Driver
LPI2C driver files are further divided into LPI2C Peripheral Abstraction Layer(PAL) & LPI2C Low Level drivers, as shown below:
- LPSPI Peripheral Abstraction Layer (PAL): contains functions and variables that are directly used in main.c or application code. And internally these functions use the LPSPI Low-level drivers & LPSPI IRQ. So if hardware is changed LPSPI PAL would remain same and only internal low-level driver files need to be changed or modified. By this way we don’t have make many changes on application level.
- LPSPI Low-level driver: contains functions that configures the LPSPI peripheral registers for initializing the peripheral, using the peripheral and processing the data of peripheral at hardware level. These files are the ones which actually interacts with the hardware and make it configurable to our needs. 
In the blogs we will be exploring the LPSPI Peripheral Abstraction Layer files (PAL) in more details, as that would be directly used in our application project development(main.c) and driver creation of I2C modules (Display Screens, Memory Cards and IoT Modules
Kunal Gupta Tweet
LPSPI PAL
In LPSPI PAL their are 4 files, 2 files for SPI master and 2 files for SPI Slave.
Let’s get into these files:
- lpspi_master_driver.h & lpspi_slave_driver.h: Contains the Enum’s, structures and function declarations that would be used in application code. Only functions which are declared in these header files can be used in main.c or application project.
- lpspi_master_driver.c & lpspi_slave_driver.c: Contains the function definitions of the declared functions (uses the low-level driver functions) along with some static functions also that are restricted to use in this file only.
Functions
LPSPI Master Functions
- LPSPI_DRV_MasterInit: This function is first function to be used in main.c or application code to initialize the SPI peripheral in Master Mode.
status_t LPSPI_DRV_MasterInit(uint32_t instance, lpspi_state_t * lpspiState,
const lpspi_master_config_t * spiConfig)
This function has 3 parameters as follows:
- instance: integer number indicating which instance of LPSPI we are going to use.
- lpspiState: Structure pointer have to be sent for lpspi_state_t structure
- spiConfig: Structure pointer have to be sent for lpspi_master_config_t
/*! @brief Device instance number */
#define INST_LPSPI_1 0U
/*******************************************************************************
* Global variables
******************************************************************************/
/* Define state structure for current SPI instance */
lpspi_state_t lpspi_1State;
/* LPSPI Master Configurations 0 */
const lpspi_master_config_t lpspi_0_MasterConfig0 = {
.bitsPerSec = 1000000UL,
.whichPcs = LPSPI_PCS0,
.pcsPolarity = LPSPI_ACTIVE_HIGH,
.isPcsContinuous = false,
.bitcount = 8U,
.lpspiSrcClk = 8000000UL,
.clkPhase = LPSPI_CLOCK_PHASE_1ST_EDGE,
.clkPolarity = LPSPI_SCK_ACTIVE_HIGH,
.lsbFirst = false,
.transferType = LPSPI_USING_DMA,
.rxDMAChannel = 0U,
.txDMAChannel = 1U,
.callback = NULL,
.callbackParam = NULL
};
/* SPI master configuration: clock speed: 500 kHz, 8 bits/frame, LSB first */
LPSPI_DRV_MasterInit(INST_LPSPI_1, &lpspi_1State, &lpspi_0_MasterConfig0);
- LPSPI_DRV_SetPcs:
status_t LPSPI_DRV_SetPcs(uint32_t instance, lpspi_which_pcs_t whichPcs, lpspi_signal_polarity_t polarity)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_MasterTransferBlocking
status_t LPSPI_DRV_MasterTransferBlocking(uint32_t instance,
const uint8_t * sendBuffer,
uint8_t * receiveBuffer,
uint16_t transferByteCount,
uint32_t timeout)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_MasterTransfer
status_t LPSPI_DRV_MasterTransfer(uint32_t instance,
const uint8_t * sendBuffer,
uint8_t * receiveBuffer,
uint16_t transferByteCount)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_MasterGetTransferStatus
status_t LPSPI_DRV_MasterGetTransferStatus(uint32_t instance, uint32_t * bytesRemained)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_MasterIRQHandler
void LPSPI_DRV_MasterIRQHandler(uint32_t instance)
{
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
LPSPI Master data Types
Their are 2 structures that are important and will be used:
- lpspi_master_config_t: This structure has members to configure the LPSPI Master according to user defined settings.
/*!
* @brief Data structure containing information about a device on the SPI bus.
*
* The user must populate these members to set up the LPSPI master and
* properly communicate with the SPI device.
* Implements : lpspi_master_config_t_Class
*/
typedef struct
{
uint32_t bitsPerSec; /*!< Baud rate in bits per second*/
lpspi_which_pcs_t whichPcs; /*!< Selects which PCS to use */
lpspi_signal_polarity_t pcsPolarity; /*!< PCS polarity */
bool isPcsContinuous; /*!< Keeps PCS asserted until transfer complete */
uint16_t bitcount; /*!< Number of bits/frame, minimum is 8-bits */
uint32_t lpspiSrcClk; /*!< Module source clock */
lpspi_clock_phase_t clkPhase; /*!< Selects which phase of clock to capture data */
lpspi_sck_polarity_t clkPolarity; /*!< Selects clock polarity */
bool lsbFirst; /*!< Option to transmit LSB first */
lpspi_transfer_type transferType; /*!< Type of LPSPI transfer */
uint8_t rxDMAChannel; /*!< Channel number for DMA rx channel. If DMA mode isn't used this field will be ignored. */
uint8_t txDMAChannel; /*!< Channel number for DMA tx channel. If DMA mode isn't used this field will be ignored. */
spi_callback_t callback; /*!< Select the callback to transfer complete */
void *callbackParam; /*!< Select additional callback parameters if it's necessary */
} lpspi_master_config_t;
const lpspi_master_config_t lpspi_0_MasterConfig0 = {
.bitsPerSec = 1000000UL,
.whichPcs = LPSPI_PCS0,
.pcsPolarity = LPSPI_ACTIVE_HIGH,
.isPcsContinuous = false,
.bitcount = 8U,
.lpspiSrcClk = 8000000UL,
.clkPhase = LPSPI_CLOCK_PHASE_1ST_EDGE,
.clkPolarity = LPSPI_SCK_ACTIVE_HIGH,
.lsbFirst = false,
.transferType = LPSPI_USING_DMA,
.rxDMAChannel = 0U,
.txDMAChannel = 1U,
.callback = NULL,
.callbackParam = NULL
};
- lpspi_state_t: This structure has data members, which keep track of the on-going transfers .
/*!
* @brief Runtime state structure for the LPSPI master driver.
*
* This structure holds data that is used by the LPSPI peripheral driver to
* communicate between the transfer function and the interrupt handler. The
* interrupt handler also uses this information to keep track of its progress.
* The user must pass the memory for this run-time state structure. The
* LPSPI master driver populates the members.
* Implements : lpspi_state_t_Class
*/
typedef struct
{
uint16_t bitsPerFrame; /*!< Number of bits per frame: 8- to 4096-bits; needed for
TCR programming */
uint16_t bytesPerFrame; /*!< Number of bytes per frame: 1- to 512-bytes */
bool isPcsContinuous; /*!< Option to keep chip select asserted until transfer
complete; needed for TCR programming */
bool isBlocking; /*!< Save the transfer type */
uint32_t lpspiSrcClk; /*!< Module source clock */
volatile bool isTransferInProgress; /*!< True if there is an active transfer */
const uint8_t * txBuff; /*!< The buffer from which transmitted bytes are taken */
uint8_t * rxBuff; /*!< The buffer into which received bytes are placed */
volatile uint16_t txCount; /*!< Number of bytes remaining to send */
volatile uint16_t rxCount; /*!< Number of bytes remaining to receive */
volatile uint16_t txFrameCnt; /*!< Number of bytes from current frame which were already sent */
volatile uint16_t rxFrameCnt; /*!< Number of bytes from current frame which were already received */
volatile bool lsb; /*!< True if first bit is LSB and false if first bit is MSB */
uint8_t fifoSize; /*!< RX/TX fifo size */
uint8_t rxDMAChannel; /*!< Channel number for DMA rx channel */
uint8_t txDMAChannel; /*!< Channel number for DMA tx channel */
lpspi_transfer_type transferType; /*!< Type of LPSPI transfer */
semaphore_t lpspiSemaphore; /*!< The semaphore used for blocking transfers */
transfer_status_t status; /*!< The status of the current */
spi_callback_t callback; /*!< Select the callback to transfer complete */
void *callbackParam; /*!< Select additional callback parameters if it's necessary */
uint32_t dummy; /*!< This field is used for the cases when TX is NULL and LPSPI is in DMA mode */
} lpspi_state_t;
/* Define state structure for current SPI instance */
lpspi_state_t lpspi_1State;
LPSPI Slave Functions
- LPSPI_DRV_SlaveInit
status_t LPSPI_DRV_SlaveInit(uint32_t instance,
lpspi_state_t * lpspiState,
const lpspi_slave_config_t * slaveConfig)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_SlaveTransferBlocking
status_t LPSPI_DRV_SlaveTransferBlocking(uint32_t instance,
const uint8_t *sendBuffer,
uint8_t *receiveBuffer,
uint16_t transferByteCount,
uint32_t timeout)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_SlaveTransfer
status_t LPSPI_DRV_SlaveTransfer(uint32_t instance,
const uint8_t *sendBuffer,
uint8_t *receiveBuffer,
uint16_t transferByteCount)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_SlaveGetTransferStatus
status_t LPSPI_DRV_SlaveGetTransferStatus(uint32_t instance,uint32_t * bytesRemained)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
- LPSPI_DRV_SlaveIRQHandler
void LPSPI_DRV_SlaveIRQHandler(uint32_t instance)
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
console.log( 'Code is Poetry' );
LPSPI Slave data Types
Their are 2 structures that are important and will be used:
- lpspi_slave_config_t: This structure has members to configure the LPSPI Slave according to user defined settings.
/*!
* @brief User configuration structure for the SPI slave driver.
* Implements : lpspi_slave_config_t_Class
*/
typedef struct
{
lpspi_signal_polarity_t pcsPolarity; /*!< PCS polarity */
uint16_t bitcount; /*!< Number of bits/frame, minimum is 8-bits */
lpspi_clock_phase_t clkPhase; /*!< Selects which phase of clock to capture data */
lpspi_which_pcs_t whichPcs;
lpspi_sck_polarity_t clkPolarity; /*!< Selects clock polarity */
bool lsbFirst; /*!< Option to transmit LSB first */
lpspi_transfer_type transferType; /*!< Type of LPSPI transfer */
uint8_t rxDMAChannel; /*!< Channel number for DMA rx channel. If DMA mode isn't used this field will be ignored. */
uint8_t txDMAChannel; /*!< Channel number for DMA tx channel. If DMA mode isn't used this field will be ignored. */
spi_callback_t callback; /*!< Select the callback to transfer complete */
void *callbackParam; /*!< Select additional callback parameters if it's necessary */
} lpspi_slave_config_t;
/* LPSPI Slave Configurations 0 */
const lpspi_slave_config_t lpspi_1_SlaveConfig0 = {
.whichPcs = LPSPI_PCS0,
.pcsPolarity = LPSPI_ACTIVE_HIGH,
.bitcount = 8U,
.clkPhase = LPSPI_CLOCK_PHASE_1ST_EDGE,
.clkPolarity = LPSPI_SCK_ACTIVE_HIGH,
.lsbFirst = false,
.transferType = LPSPI_USING_DMA,
.rxDMAChannel = 2U,
.txDMAChannel = 3U,
.callback = NULL,
.callbackParam = NULL
};
SPI Demo Code in S32K144 MCU
/*
* Copyright 2020 NXP
* All rights reserved.
*
* NXP Confidential. This software is owned or controlled by NXP and may only be
* used strictly in accordance with the applicable license terms. By expressly
* accepting such terms or by downloading, installing, activating and/or otherwise
* using the software, you are agreeing that you have read, and that you agree to
* comply with and are bound by, such license terms. If you do not agree to be
* bound by the applicable license terms, then you may not retain, install,
* activate or otherwise use the software. The production use license in
* Section 2.3 is expressly granted for this software.
*/
/*!
** @file main.c
** @brief
** Main module.
** This module contains user's application code.
*/
/*!
** @addtogroup main_module main module documentation
** @{
*/
/* MODULE main */
/* Including necessary configuration files. */
#include "sdk_project_config.h"
volatile int exit_code = 0;
/* User includes */
#include
#include
#define TIMEOUT 1000U
#define NUMBER_OF_FRAMES 100U
#define RED_LED (1<<15U) /* RED_LED PTD[15] */
#define GPIO_LED PTD
/* Struct that defines RX and TX buffer arrays */
typedef struct
{
uint8_t tx[NUMBER_OF_FRAMES];
uint8_t rx[NUMBER_OF_FRAMES];
} spi_buffer_t;
/*!
* @brief Initialize the SPI buffer with different values for TX/RX
*
* @param spiBuffer Pointer to the buffer that will be initialized
* @param master True if the buffer is used with the master device,
* False if not
*/
void InitSPIBuffer(spi_buffer_t * spiBuffer, bool master)
{
uint8_t cnt;
/* Fill the buffers */
for(cnt = 0U; cnt < NUMBER_OF_FRAMES; cnt++)
{
/* If the master flag is set, then the txBuffer will take the value of the counter,
* else the value will be (TRANSFER_SIZE - Counter).
* This approach is taken to make the data transfer more visible.
*/
spiBuffer->tx[cnt] = ((master == true) ? (cnt) : (NUMBER_OF_FRAMES - cnt));
spiBuffer->rx[cnt] = 0U;
}
}
/*!
\brief The main function for the project.
\details The startup initialization sequence is the following:
* - startup asm routine
* - main()
*/
int main(void)
{
/* Write your local variable definition here */
spi_buffer_t master_buffer, slave_buffer;
InitSPIBuffer(&master_buffer, true);
InitSPIBuffer(&slave_buffer, false);
/* Variable used for the loop that checks the data buffers */
uint8_t i;
uint8_t frame_sent = 1;
/* Initialize and configure clocks
* - see clock manager component for details
*/
CLOCK_SYS_Init(g_clockManConfigsArr, CLOCK_MANAGER_CONFIG_CNT, g_clockManCallbacksArr, CLOCK_MANAGER_CALLBACK_CNT);
CLOCK_SYS_UpdateConfiguration(0U, CLOCK_MANAGER_POLICY_AGREEMENT);
/* Initialize pins
* - See PinSettings component for more info
*/
PINS_DRV_Init(NUM_OF_CONFIGURED_PINS0, g_pin_mux_InitConfigArr0);
/* Initialize DMA */
EDMA_DRV_Init(&dmaController_State, &dmaController_InitConfig, edmaChnStateArray, edmaChnConfigArray, EDMA_CONFIGURED_CHANNELS_COUNT);
/* SPI master configuration: clock speed: 500 kHz, 8 bits/frame, LSB first */
LPSPI_DRV_MasterInit(INST_LPSPI_1, &lpspi_1State, &lpspi_0_MasterConfig0);
/* SPI slave configuration: 8 bits/frame, LSB first */
LPSPI_DRV_SlaveInit(INST_LPSPI_2, &lpspi_2State, &lpspi_1_SlaveConfig0);
/* Configure delay between transfer, delay between SCK and PCS and delay between PCS and SCK */
LPSPI_DRV_MasterSetDelay(INST_LPSPI_1, 1, 1, 1);
while(1)
{
LPSPI_DRV_SlaveTransfer(INST_LPSPI_2, slave_buffer.tx,slave_buffer.rx, NUMBER_OF_FRAMES);
/* Start the blocking transfer */
LPSPI_DRV_MasterTransferBlocking(INST_LPSPI_1, master_buffer.tx,master_buffer.rx, NUMBER_OF_FRAMES, TIMEOUT);
for(i=0;i
lpspi_dma_s32k144 Demo Code Explanation
FTM PWM Driver API’s of NXP S32K144 MCU
API Name: Ftm_Pwm_Ip_Init() void Ftm_Pwm_Ip_Init(uint8 Instance,const Ftm_Pwm_Ip_UserCfgType * UserCfg) Role: This API initializes the FTM peripheral according to PWM feature. Author: Kunal Gupta
SAR ADC Explained!
Why to learn about SAR ADC? SAR ADC is a standard AUTOSAR opts for. That’s why you see most of the automotive microcontrollers can be observed having SAR ADC and SAR stands for Successive Approximation Register. You can see the below picture which I extracted for verification of this fact. Microcontrollers which are verified: NXP S32K1xx Series NXP MPC5xxx Series STMicroelectronics SPC5 Series Renesas RH850 Series Infineon AURIX TC3xx Series Microchip PIC32 Series Why AUTOSAR likes SAR ADC over others? SAR ADC working is most suitable due to three major factors mentioned below: High Conversion Speed with Accuracy: SAR ADCs are fast to handle conversion like real-time sensor data conversion while holding its precision as it is. This conversion can be like throttle control, battery management, and other critical functions. Power Efficiency: Power consumption is one of the most important factors in any automotive application especially electric vehicles. SAR ADC consumes comparatively less power rather than ADC like FLASH ADC. Scalability: SAR ADCs offer a trade-off between speed, resolution, and area, which is crucial in automotive designs where space and performance both matter. Comparing SAR ADC with Flash and Sigma-Delta ADC Flash ADC: The fastest type of ADC, converting signals in just one clock cycle. This speed comes at the cost of power consumption and size, as it requires one comparator per bit of resolution. Given the complex needs of automotive systems, this increased power draw and large footprint make Flash ADCs impractical for most real-time automotive control systems. Sigma-Delta ADC: Offers exceptional accuracy by oversampling the input signal and using noise-shaping techniques. However, its conversion speed is much slower compared to SAR ADCs. This makes it unsuitable for fast, real-time sensor data processing, though it shines in applications where high precision is needed, such as audio or pressure measurement. SAR ADC stands between these two, offering sufficient speed, accuracy, and power efficiency. This balance makes it the top choice for most automotive microcontroller designs, especially in safety-critical applications like engine control, where both speed and accuracy matter. How SAR ADC Works Sample & Hold (S/H) Block: This block holds the analog input signal steady while the ADC performs the conversion. The process begins by capturing the input voltage and freezing it momentarily to allow precise comparisons during the conversion. Comparator: The comparator checks the DAC’s output against the input signal at every stage of the conversion. It decides whether the next bit in the SAR register should be a ‘1’ or a ‘0’ based on whether the input signal is greater or lesser than the DAC output. SAR Register: This is a shift register that stores the output bit by bit as the conversion proceeds. The SAR register’s value evolves with each step of the approximation process, eventually containing the final digital equivalent of the input analog signal. DAC (Digital-to-Analog Converter): The DAC generates a voltage based on the digital bits already stored in the SAR register. The comparator then compares this DAC output with the input signal. The DAC’s resolution is crucial since it must match the resolution of the SAR ADC. Step-by-Step Example: How SAR ADC Calculates an Input Signal Example Setup: Reference Voltage (Vref): 5V Resolution: 4 bits (for simplicity) Input Voltage (Vin): 2.6V Author: Rohan Singhal
Reading ADC Values via ADC Driver of Autosar MCAL layer using ElecronicsV3 Board( NXP S32K144 MCU)
Author: Kunal Gupta
Autosar MCAL layer ADC Driver API’s and data types explanation
API name: Adc_Init() void Adc_Init (const Adc_ConfigType* ConfigPtr) Role: Adc_Init() API initializes the ADC peripheral of the microcontroller. This API is universal and used across all automotive MCUs for initializing the ADC peripheral of the corresponding MCU. This API initializes the registers of ADC peripherals internally. So function definition of Adc_Init () would be different for different SoCs. But in applications across all automotive MCUs, this API name and syntax would be used to initialize the ADC peripheral according to the Adc_ConfigType structure. Working of this API: This API calls the low-level functions that configure the ADC clock, prescaler, and trigger mode. This API initializes all the ADC instances, according to their configurations for ADC Hardware Unit. This API does not configure the pins of the MCU to analog pins. That part has to be done by the Port or MCU driver. Parameter passed: The parameter that is passed to this API is of Adc_ConfigType data type. Adc_ConfigType is a structure that contains the set of configuration parameters for initializing the ADC Driver and ADC HW units. The object of this data type is generated and defined by the configurator tool. We users don’t have to initialize this object. It is automatically configured based on the configuration we do on the GUI. We just have to send the object of Adc_ConfigType with ampersand (&) to this API. Chronology to use this API: This API is used in the beginning of main(). Just after the system clock and ADC pins are configured by their respective APIs. Return value: This function does not return anything. As it only initializes the internal peripheral registers. But just to check and verify the function, you can observe the changes in ADC HW unit registers just after executing this function. Syntax to use this API: Adc_Init(&Adc_Config_VS_0); ADC Peripheral Registers affected by This API, with respect to S32K144 MCU using ElecronicsV3 Board: API Name: Adc_EnableGroupNotification void Adc_EnableGroupNotification(Adc_GroupType Group) Role: This API, enables the notification feature when conversion of all channels of the ADC group is successfully converted. Working of this API: After starting the ADC conversion either by software trigger or hardware trigger, the group notification function will be called only if its group notification is enabled. And that thing is done by this API. That’s why, in this API we just send one parameter, Group Number. The ADC Group Notification callback function is called from the IRQ handler of ADC. ADC MCAL layer has a defined IRQ notification callback function, that is called when the IRQ handler of ADC is invoked upon successful conversion of ADC channels. And IRQ Notification callback function calls the group-specific notification callback and updates the Group Status to ADC Completed/ADC Stream Completed. For a single ADC hardware unit in a microcontroller, ADC IRQ is the same for all channels. So upon successful conversion of ADC of a channel IRQ handler notification is called, into which analysis is done that which channel of which group is completed and corresponding to that group notification callback is invoked. Parameter passed: The parameter that is passed to this API is of Adc_GroupType data type. Adc_GroupType is a typedef of uint16. It is just a numeric ID ( 1,2,3,4 etc), denoting the ADC group number. The values of Group IDs are generated and initialized by the code configurator tool. We users don’t have to initialize the group ID number. The group IDs are automatically macro-defined based on the GUI configuration tool. We just have to send the macro-defined group name in this API. Prerequisite: ADC should be used with Interrupts capability. If no interrupts are used, no notification capability will be invoked. The ADC Notification capability checkbox has to be checked in the AdcGeneral section of the ADC configurator tool. If this is not checked, the notification capability will not work. Make sure that we have configured the ADC Group Notification function in the configurator tool while configuring the ADC groups. // photo The name that would be written over here, the function of that name only will be created in generated files and we can define the function in the application code on how to use it and what to do. Chronology: This API is used just after the Adc_init () and before calling the application loop that involves the use of ADC conversion. Return value: This function does not return anything. As it only initializes the internal state to enable the notifications. Syntax to use this API: Adc_EnableGroupNotification(AdcGroup_0); API Name: Adc_StartGroupConversion() void Adc_StartGroupConversion(Adc_GroupType Group) Role: This API initializes the conversion of channels of the group which is triggered by software. This API starts the conversion of the ADC group which is configured to get triggered via a Software Trigger. Hardware Trigger ADC groups are not started via this API. After the usage of this API, the ADC conversion of channels that are referred by a single group would begin, and we can expect corresponding group notifications to be called. And to see the results of ADC conversion we can use the Adc_ReadGroup(). Working: This API initializes the internal ADC peripheral register of ADC channels of the group which has to be converted. It also writes on those peripheral registers which starts the ADC conversion by Software trigger. Parameters: The parameter that is passed to this API is of Adc_GroupType data type. Adc_GroupType is a typedef of uint16. It is just a numeric ID ( 1,2,3,4 etc), denoting the ADC group number. The values of Group IDs are generated and initialized by the configurator tool. We users don’t have to initialize the group ID number. The group IDs are automatically macro-defined based on the GUI configuration tool. We just have to send the macro-defined group name in this API. Prerequisite: The ADC module should be initialized with Adc_Init() API and ADC notifications of the group should be enabled. Syntax to this API: Adc_StartGroupConversion(AdcGroup_0); API Name: Adc_EnableHardwareTrigger() void Adc_EnableHardwareTrigger(Adc_GroupType Group) Role: This API initializes the conversion of channels of the group
Analog to Digital Module of Autosar MCAL Layer
ADC Driver of Autosar MCAL layer Explanation, Understanding and tutorial using ElecronicsV3 Development board
Author: Kunal Gupta
Author