5.19 SPI Guide
The N32G41x SPI module performs synchronous serial communication in half-duplex, full-duplex or single-line bidirectional mode, with master, slave and multi-master support, programmable bit order, hardware or software NSS management, configurable clock polarity and phase and hardware CRC verification; the driver consists of the header n32g41x_spi.h and the implementation file n32g41x_spi.c, exposing 25 public functions. This SDK provides 8 examples under projects/n32g41x_EVAL/examples/SPI/, covering full-duplex and single-line transfer, 9-bit data frames, interrupt and DMA transfer and access to an external SPI FLASH. Read the register bit definitions, the working modes and the CRC and error-flag handling together with the “Serial peripheral interface / Inter-integrated sound (SPI/I2S)” chapter of the user manual, and take the errata sheet as the authority on known chip issues.
5.19.1 Related files
| Relative path | Purpose |
|---|---|
firmware/n32g41x_std_periph_driver/inc/n32g41x_spi.h |
Defines the SPI_InitType structure and the macros for data direction, master/slave mode, frame length, clock polarity and phase, NSS, baud-rate prescaler, bit order and CRC polynomial, and declares 25 public functions in the seven sections reset and initialization, data transfer, NSS management, CRC, sampling delay, DMA and interrupt and flag management |
firmware/n32g41x_std_periph_driver/src/n32g41x_spi.c |
Implements those 25 functions: peripheral reset and initialization, data transmission and reception, frame-length and bidirectional-direction configuration, NSS and polarity configuration, CRC calculation and retrieval, sampling-delay write, DMA and interrupt enabling, and reading and clearing of status and interrupt flags |
projects/n32g41x_EVAL/examples/SPI/ |
SPI example root directory, containing the 8 examples CRC, FullDuplex_SoftNSS, Simplex_Interrupt, SPI_9Bit, SPI_DMA_T&R, SPI_FLASH, SPI_FLASH_DMA and SPI_Simplex_DMA_Recv |
projects/n32g41x_EVAL/examples/SPI/SPI_FLASH/ |
Directory layout of that example: MDK-ARM/, EWARM/, inc/, src/, readme.txt |
projects/n32g41x_EVAL/bsp/ |
Board support files referenced by the examples: evaluation-board LED and key definitions and the millisecond and microsecond delay functions |
5.19.2 API reference
The following 25 public functions are listed in the declaration order of n32g41x_spi.h in 7 groups; each entry gives a summary and the original comment above the function in n32g41x_spi.c.
Reset and initialization
SPI_I2S_DeInit, SPI_Init, SPI_InitStruct, SPI_Enable
Data transfer
SPI_I2S_TransmitData, SPI_I2S_ReceiveData, SPI_ConfigDataLen, SPI_ConfigBidirectionalMode
NSS management
SPI_SetNssLevel, SPI_SSOutputEnable, SPI_NssPolSet, SPI_NSSFailCRCStopEnable
CRC
SPI_TransmitCrcNext, SPI_TransmitCrcNextCmd, SPI_EnableCalculateCrc, SPI_GetCRCDat, SPI_GetCRCPoly
Sampling delay
SPI_DelayTime_Set, SPI_DelayTime_Get
DMA
SPI_I2S_EnableDma
Interrupt and flag management
SPI_I2S_EnableInt, SPI_I2S_GetStatus, SPI_ClrCRCErrFlag, SPI_I2S_GetIntStatus, SPI_I2S_ClrITPendingBit
void SPI_I2S_DeInit(const SPI_Module *SPIx)Reset the SPI/I2S registers.
name SPI_I2S_DeInit.
fun Reset the SPI/I2S registers.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
return none
void SPI_Init(SPI_Module *SPIx, const SPI_InitType *SPI_InitParam)Initializes the SPIx peripheral according to the specified parameters in the SPI_InitStruct.
name SPI_Init.
fun Initializes the SPIx peripheral according to the specified parameters in the SPI_InitStruct.
param SPIx :
- SPI1
- SPI2
param SPI_InitParam :
- DataDirection
- SPI_DIR_DOUBLELINE_FULLDUPLEX
- SPI_DIR_DOUBLELINE_RONLY
- SPI_DIR_SINGLELINE_RX
- SPI_DIR_SINGLELINE_TX
- SpiMode
- SPI_MODE_MASTER
- SPI_MODE_SLAVE
- DataLen
- SPI_DATA_SIZE_16BITS
- SPI_DATA_SIZE_8BITS
- SPI_DATA_SIZE_9BITS (Only valid for SPI2)
- CLKPOL
- SPI_CLKPOL_LOW
- SPI_CLKPOL_HIGH
- CLKPHA
- SPI_CLKPHA_FIRST_EDGE
- SPI_CLKPHA_SECOND_EDGE
- NSS
- SPI_NSS_SOFT
- SPI_NSS_HARD
- BaudRatePres
- SPI_BR_PRESCALER_2
- SPI_BR_PRESCALER_4
- SPI_BR_PRESCALER_8
- SPI_BR_PRESCALER_16
- SPI_BR_PRESCALER_32
- SPI_BR_PRESCALER_64
- SPI_BR_PRESCALER_128
- SPI_BR_PRESCALER_256
- FirstBit
- SPI_FB_MSB
- SPI_FB_LSB
- CRCPoly default 0x0007, max 0xffff
return none
Use note: The implementation first clears the data-direction, NSS, frame-length, bit-order, clock-polarity and phase and baud-rate-prescaler bits of SPI_CTRL1 and the DATFF9 bit of SPI_CTRL2; it writes the DATFF9 bit of SPI_CTRL2 only when SPIx is SPI2 and DataLen is SPI_DATA_SIZE_9BITS, and otherwise writes DataLen into the DATFF field of SPI_CTRL1. At the end it also clears the I2S enable and mode bits of SPI_I2S_CFGR, fixing the peripheral in SPI mode.
void SPI_InitStruct(SPI_InitType *SPI_StructInit)Fills each SPI_InitStruct member with its default value.
name SPI_InitStruct.
fun Fills each SPI_InitStruct member with its default value.
param SPI_StructInit (The input parameters must be the following values):
- DataDirection
- SpiMode
- DataLen
- CLKPOL
- CLKPHA
- NSS
- BaudRatePres
- FirstBit
- CRCPoly
return none
void SPI_Enable(SPI_Module *SPIx, FunctionalState Cmd)Enables or disables the specified SPI peripheral.
name SPI_Enable.
fun Enables or disables the specified SPI peripheral.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void SPI_I2S_TransmitData(SPI_Module *SPIx, uint16_t Data)Transmits a Data through the SPIx/I2Sx peripheral.
name SPI_I2S_TransmitData.
fun Transmits a Data through the SPIx/I2Sx peripheral.
param SPIx :
- SPI2
- SPI1
param Data: Data to be transmitted
return none
uint16_t SPI_I2S_ReceiveData(const SPI_Module *SPIx)Get SPI/I2S data from DAT register.
name SPI_I2S_ReceiveData.
fun Get SPI/I2S data from DAT register.
param SPIx (The input parameters must be the following values):
- SPI2
- SPI1
return The data in the SPI_DAT register
void SPI_ConfigDataLen(SPI_Module *SPIx, uint16_t DataLen)Configures the data size for the selected SPI.
name SPI_ConfigDataLen.
fun Configures the data size for the selected SPI.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param DataLen (The input parameters must be the following values):
- SPI_DATA_SIZE_16BITS
- SPI_DATA_SIZE_8BITS
- SPI_DATA_SIZE_9BITS (Only valid for SPI2)
return none
Use note: The implementation rewrites only one of the DATFF9 bit of SPI_CTRL2 and the DATFF field of SPI_CTRL1: it writes the DATFF9 bit of SPI_CTRL2 only when SPIx is SPI2 and DataLen is SPI_DATA_SIZE_9BITS, and otherwise writes DataLen into the DATFF field of SPI_CTRL1.
void SPI_ConfigBidirectionalMode(SPI_Module *SPIx, uint32_t DataDirection)Selects the data transfer direction in bi-directional mode for the specified SPI.
name SPI_ConfigBidirectionalMode.
fun Selects the data transfer direction in bi-directional mode for the specified SPI.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param DataDirection (The input parameters must be the following values):
- SPI_BIDIRECTION_RX
- SPI_BIDIRECTION_TX
return none
void SPI_SetNssLevel(SPI_Module *SPIx, uint32_t SPI_NSSInternalSoft)Configures internally by software the NSS pin for the selected SPI.
name SPI_SetNssLevel.
fun Configures internally by software the NSS pin for the selected SPI.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param SPI_NSSInternalSoft (The input parameters must be the following values):
- SPI_NSS_HIGH Set NSS pin internally
- SPI_NSS_LOW Reset NSS pin internally
return none
void SPI_SSOutputEnable(SPI_Module *SPIx, FunctionalState Cmd)Enables or disables the SS output for the selected SPI.
name SPI_SSOutputEnable.
fun Enables or disables the SS output for the selected SPI.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void SPI_NssPolSet(SPI_Module *SPIx, uint16_t Level)NSS Polarity control.
name SPI_NssPolSet.
fun NSS Polarity control.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param Level (The input parameters must be the following values):
- NSS_LOW_LEVEL_VALID
- NSS_HIGH_LEVEL_VALID
return none
void SPI_NSSFailCRCStopEnable(SPI_Module *SPIx, FunctionalState Cmd)NSS failure, CRC calculation stops.
name SPI_NSSFailCRCStopEnable.
fun NSS failure, CRC calculation stops.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param Cmd (The input parameters must be the following values):
- ENABLE: stop CRC calculation
- DISABLE: continue CRC calculation
return none
void SPI_TransmitCrcNext(SPI_Module *SPIx)Transmit the SPIx CRC value.
name SPI_TransmitCrcNext.
fun Transmit the SPIx CRC value.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
return none
void SPI_TransmitCrcNextCmd(SPI_Module *SPIx, FunctionalState Cmd)Enables or disables the transmission of the SPIx CRC value.
name SPI_TransmitCrcNextCmd.
fun Enables or disables the transmission of the SPIx CRC value.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void SPI_EnableCalculateCrc(SPI_Module *SPIx, FunctionalState Cmd)Enables or disables the CRC value calculation of the transferred bytes.
name SPI_EnableCalculateCrc.
fun Enables or disables the CRC value calculation of the transferred bytes.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
uint32_t SPI_GetCRCDat(const SPI_Module *SPIx, uint8_t SPI_CRC)Get SPI CRC data from SPI_CRCTDAT/SPI_CRCRDAT register.
name SPI_GetCRCDat.
fun Get SPI CRC data from SPI_CRCTDAT/SPI_CRCRDAT register.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param SPI_CRC (The input parameters must be the following values):
- SPI_CRC_TX
- SPI_CRC_RX
return Tx/Rx CRC register value.
uint32_t SPI_GetCRCPoly(const SPI_Module *SPIx)Get CRC Polynomial from SPI_CRCPOLY register.
name SPI_GetCRCPoly.
fun Get CRC Polynomial from SPI_CRCPOLY register.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
return The CRC Polynomial register value.
void SPI_DelayTime_Set(SPI_Module *SPIx, uint16_t DelayTime)SPI Master Sampling Delay Time Configuration.
name SPI_DelayTime_Set.
fun SPI Master Sampling Delay Time Configuration.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param DelayTime (The input parameters must be the following values):
- SPI_MASTER_SAMPLING_DELAY_BYPASS
- SPI_MASTER_SAMPLING_DELAY_1_2_CLK
- SPI_MASTER_SAMPLING_DELAY_2_2_CLK
- SPI_MASTER_SAMPLING_DELAY_3_2_CLK
- SPI_MASTER_SAMPLING_DELAY_4_2_CLK
- SPI_MASTER_SAMPLING_DELAY_5_2_CLK
- SPI_MASTER_SAMPLING_DELAY_6_2_CLK
- SPI_MASTER_SAMPLING_DELAY_7_2_CLK
- SPI_MASTER_SAMPLING_DELAY_8_2_CLK
- SPI_MASTER_SAMPLING_DELAY_9_2_CLK
- SPI_MASTER_SAMPLING_DELAY_10_2_CLK
- SPI_MASTER_SAMPLING_DELAY_11_2_CLK
- SPI_MASTER_SAMPLING_DELAY_12_2_CLK
- SPI_MASTER_SAMPLING_DELAY_13_2_CLK
- SPI_MASTER_SAMPLING_DELAY_14_2_CLK
- SPI_MASTER_SAMPLING_DELAY_15_2_CLK
return none
note only be configured in SPI master full-duplex and SPI master receive mode
uint16_t SPI_DelayTime_Get(const SPI_Module *SPIx)Get SPI master clock delay time.
name SPI_DelayTime_Get.
fun Get SPI master clock delay time.
param SPIx :
- SPI1
- SPI2
return The clock delay time.
void SPI_I2S_EnableDma(SPI_Module *SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState Cmd)Enables or disables the SPIx/I2Sx DMA interface.
name SPI_I2S_EnableDma.
fun Enables or disables the SPIx/I2Sx DMA interface.
param SPIx :
- SPI2
- SPI1
param SPI_I2S_DMAReq :
- SPI_I2S_DMA_TX
- SPI_I2S_DMA_RX
param Cmd :
- ENABLE
- DISABLE
return none
void SPI_I2S_EnableInt(SPI_Module *SPIx, uint8_t SPI_I2S_IT, FunctionalState Cmd)Enables or disables the specified SPI/I2S interrupts.
name SPI_I2S_EnableInt.
fun Enables or disables the specified SPI/I2S interrupts.
param SPIx :
- SPI1
- SPI2
param SPI_I2S_IT :
- SPI_I2S_INT_TE
- SPI_I2S_INT_RNE
- SPI_I2S_INT_ERR
param Cmd :
- ENABLE
- DISABLE
return none
FlagStatus SPI_I2S_GetStatus(const SPI_Module *SPIx, uint16_t SPI_I2S_FLAG)Checks whether the specified SPI/I2S flag is set or not.
name SPI_I2S_GetStatus.
fun Checks whether the specified SPI/I2S flag is set or not.
param SPIx :
- SPI1
- SPI2
param SPI_I2S_FLAG :
- SPI_I2S_TE_FLAG
- SPI_I2S_RNE_FLAG
- SPI_I2S_BUSY_FLAG
- SPI_CRCERR_FLAG
- SPI_MODERR_FLAG
- SPI_I2S_OVER_FLAG
- I2S_UNDER_FLAG
- I2S_CHSIDE_FLAG
return The new state of SPI_I2S_FLAG (SET or RESET).
void SPI_ClrCRCErrFlag(SPI_Module *SPIx, uint32_t SPI_FLAG)Clear SPI flag status.
name SPI_ClrCRCErrFlag.
fun Clear SPI flag status.
param SPIx (The input parameters must be the following values):
- SPI1
- SPI2
param SPI_FLAG (The input parameters must be the following values):
- SPI_CRCERR_FLAG
return none
note
- OVER (OverRun error) flag is cleared by software sequence: a read
operation to SPI_DAT register (SPI_I2S_ReceiveData()) followed by a read
operation to SPI_STS register (SPI_I2S_GetStatus()).
- UNDER (UnderRun error) flag is cleared by a read operation to
SPI_STS register (SPI_I2S_GetStatus()).
- MODERR (Mode Fault) flag is cleared by software sequence: a read/write
operation to SPI_STS register (SPI_I2S_GetStatus()) followed by a
write operation to SPI_CTRL1 register (SPI_Enable() to enable the SPI).
INTStatus SPI_I2S_GetIntStatus(const SPI_Module *SPIx, uint8_t SPI_I2S_IT)Checks whether the specified SPI/I2S interrupt has occurred or not.
name SPI_I2S_GetIntStatus.
fun Checks whether the specified SPI/I2S interrupt has occurred or not.
param SPIx :
- SPI1
- SPI2
param SPI_I2S_IT :
- SPI_I2S_INT_TE
- SPI_I2S_INT_RNE
- SPI_I2S_INT_OVER
- SPI_INT_MODERR
- SPI_INT_CRCERR
- I2S_INT_UNDER
return The new state of SPI_I2S_IT (SET or RESET).
Use note: The implementation checks both the corresponding event flag in SPI_STS and the corresponding interrupt enable bit in SPI_CTRL2, and returns SET only when both are set.
void SPI_I2S_ClrITPendingBit(SPI_Module *SPIx, uint8_t SPI_I2S_IT)Clears the SPIx CRC Error (CRCERR) interrupt pending bit.
name SPI_I2S_ClrITPendingBit.
fun Clears the SPIx CRC Error (CRCERR) interrupt pending bit.
param SPIx :
- SPI1
- SPI2
param SPI_I2S_IT :
- SPI_INT_CRCERR
return none.
Use note: The implementation takes the low 4 bits of SPI_I2S_IT as the bit index and writes the complement of that bit into SPI_STS to clear the pending bit.
5.19.3 Demo descriptions
This section describes the 8 SPI examples: CRC connects two peripherals to verify hardware CRC, FullDuplex_SoftNSS swaps the master and slave roles on one pair of peripherals, Simplex_Interrupt shows single-line interrupt transfer, SPI_9Bit shows 9-bit data frames, SPI_DMA_T&R shows single-line DMA transfer, SPI_FLASH and SPI_FLASH_DMA access an external serial FLASH by polling and by DMA respectively, and SPI_Simplex_DMA_Recv shows single-line DMA receive.
5.19.3.1 CRC Hardware CRC verification
Example directory: projects/n32g41x_EVAL/examples/SPI/CRC/
Initialize the clocks, the SPI pin alternate functions and the print UART→Configure SPI1 as master and SPI2 as slave and enable CRC calculation→Exchange 16-bit data in a loop→After the exchange each side sends its own CRC→Compare the data and the CRC error flag→Print the CRC1 and CRC2 results
| Item | Source analysis |
|---|---|
| Function | SPI1 acts as master and SPI2 as slave, exchanging 32 half-words in 16-bit full-duplex mode; both enable hardware CRC calculation and, after the last data word, each sends its own CRC value, so the receiver compares both the data and the CRC and checks the CRC error flag. |
| Source files | src/main.c: clock and pin configuration, master/slave initialization, CRC-calculation enabling, the transfer loop and the result comparison;inc/main.h: the SPI_MASTER/SPI_SLAVE macros and the pin and buffer-length macros;readme.txt: function, environment and wiring notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Both sides use SPI_DIR_DOUBLELINE_FULLDUPLEX, SPI_DATA_SIZE_16BITS, SPI_NSS_SOFT, SPI_CLKPOL_HIGH, SPI_CLKPHA_FIRST_EDGE, SPI_FB_MSB and SPI_BR_PRESCALER_128, with CRCPoly 7 and a buffer length of 32;SPI1 is SPI_MODE_MASTER and SPI2 is SPI_MODE_SLAVE. The system clock is HSI+PLL, 80 MHz for N32G412 and 96 MHz for N32G415. |
| Program and data path | main first enables CRC calculation and the SPI peripherals, then loops 31 times: after the master transmit buffer becomes empty it writes the slave and master data in turn and reads the received data of both sides;after the loop it sends the last data word and calls SPI_TransmitCrcNext on both sides, then compares the data with Buffercmp, checks SPI_CRCERR_FLAG and reads both CRC values. |
| Main APIs | SPI_InitStruct, SPI_Init, SPI_Enable, SPI_EnableCalculateCrc, SPI_I2S_TransmitData, SPI_I2S_ReceiveData, SPI_TransmitCrcNext, SPI_I2S_GetStatus |
| Verification | The UART prints This is SPI CRC demo!!;on success it prints Test PASS!! with the hexadecimal CRC1 and CRC2 values, on failure Test fail!! with Status1 and Status2.Wire PA5, PA6 and PA7 of SPI1 to PB13, PB14 and PB15 of SPI2 respectively and connect a common ground. |
| Use note | CRC is only valid in full-duplex mode, computed as CRC8 for 8-bit frames and CRC16 for 16-bit frames; setting SPI_EnableCalculateCrc clears the CRC registers.Before reading a CRC value, wait until the corresponding CRC data has arrived, because reading a CRC register while BUSY is 1 may return a wrong value. |
Core-code walkthrough and porting
The example connects two SPI peripherals, exchanges one data set in both directions and appends a CRC to each side, so the receiver compares both the data and the CRC error flag.
Data path: the SPI_InitType configuration of SPI_MASTER and SPI_SLAVE → SPI_Init → SPI_EnableCalculateCrc → the SPI_I2S_TransmitData/SPI_I2S_ReceiveData transfer loop → SPI_TransmitCrcNext sends the CRC → Buffercmp and SPI_CRCERR_FLAG decide the result
1. Initialize master and slave as 16-bit full-duplex. Master and slave share one structure, and only SpiMode differs.
/* SPI_MASTER configuration: full-duplex, master, 16-bit */
SPI_InitStruct(&SPI_InitStructure);
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_16BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_128;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI_MASTER, &SPI_InitStructure);
/* SPI_SLAVE configuration: full-duplex, slave, 16-bit */
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_Init(SPI_SLAVE, &SPI_InitStructure);
2. Exchange data in a loop and keep the last word for the CRC. The loop condition is TxIdx < BufferSize - 1, and the last data word is handled outside the loop so that the CRC can follow it immediately.
/* Enable CRC calculation */
SPI_EnableCalculateCrc(SPI_MASTER, ENABLE);
SPI_EnableCalculateCrc(SPI_SLAVE, ENABLE);
/* Enable SPI peripherals */
SPI_Enable(SPI_MASTER, ENABLE);
SPI_Enable(SPI_SLAVE, ENABLE);
/* Transfer procedure */
while (TxIdx < BufferSize - 1)
{
/* Wait for SPI_MASTER Tx buffer empty */
while (SPI_I2S_GetStatus(SPI_MASTER, SPI_I2S_TE_FLAG) == RESET)
;
/* Send SPI_SLAVE data */
SPI_I2S_TransmitData(SPI_SLAVE, SPI_SLAVE_Buffer_Tx[TxIdx]);
/* Send SPI_MASTER data */
SPI_I2S_TransmitData(SPI_MASTER, SPI_MASTER_Buffer_Tx[TxIdx++]);
/* Wait for SPI_SLAVE data reception */
while (SPI_I2S_GetStatus(SPI_SLAVE, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read SPI_SLAVE received data */
SPI_SLAVE_Buffer_Rx[RxIdx] = (uint16_t)SPI_I2S_ReceiveData(SPI_SLAVE);
/* Wait for SPI_MASTER data reception */
while (SPI_I2S_GetStatus(SPI_MASTER, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read SPI_MASTER received data */
SPI_MASTER_Buffer_Rx[RxIdx++] = (uint16_t)SPI_I2S_ReceiveData(SPI_MASTER);
}
3. Send both CRCs after the exchange. Wait for the transmit buffers to empty, send the last data word and request the CRC transmission right away, then wait for the peer CRC.
/* Wait for SPI_MASTER Tx buffer empty */
while (SPI_I2S_GetStatus(SPI_MASTER, SPI_I2S_TE_FLAG) == RESET)
;
/* Wait for SPI_SLAVE Tx buffer empty */
while (SPI_I2S_GetStatus(SPI_SLAVE, SPI_I2S_TE_FLAG) == RESET)
;
/* Send last SPI_SLAVE data */
SPI_I2S_TransmitData(SPI_SLAVE, SPI_SLAVE_Buffer_Tx[TxIdx]);
/* Enable SPI_SLAVE CRC transmission */
SPI_TransmitCrcNext(SPI_SLAVE);
/* Send last SPI_MASTER data */
SPI_I2S_TransmitData(SPI_MASTER, SPI_MASTER_Buffer_Tx[TxIdx]);
/* Enable SPI_MASTER CRC transmission */
SPI_TransmitCrcNext(SPI_MASTER);
4. Compare the data, check the CRC error flag and read the CRC values. Buffercmp compares the receive and transmit buffers, either side setting SPI_CRCERR_FLAG marks a failure, and finally SPI_I2S_ReceiveData reads the peer CRC.
/* Check the received data with the sent ones */
TransferStatus1 = Buffercmp(SPI_SLAVE_Buffer_Rx, SPI_MASTER_Buffer_Tx, BufferSize);
TransferStatus2 = Buffercmp(SPI_MASTER_Buffer_Rx, SPI_SLAVE_Buffer_Tx, BufferSize);
/* Test on the SPI_MASTER CRC Error flag */
if ((SPI_I2S_GetStatus(SPI_MASTER, SPI_CRCERR_FLAG)) == SET)
{
TransferStatus2 = FAILED;
}
/* Test on the SPI_SLAVE CRC Error flag */
if ((SPI_I2S_GetStatus(SPI_SLAVE, SPI_CRCERR_FLAG)) == SET)
{
TransferStatus1 = FAILED;
}
/* Read SPI_MASTER received CRC value */
CRC1Value = (uint16_t)SPI_I2S_ReceiveData(SPI_MASTER);
/* Read SPI_SLAVE received CRC value */
CRC2Value = (uint16_t)SPI_I2S_ReceiveData(SPI_SLAVE);
Corresponding source files: src/main.c, inc/main.h
5.19.3.2 FullDuplex_SoftNSS Full-duplex with software NSS
Example directory: projects/n32g41x_EVAL/examples/SPI/FullDuplex_SoftNSS/
Initialize the clocks, the pin alternate functions and the print UART→Phase one: SPI1 as master and SPI2 as slave exchange 8-bit data→Compare the transfer results→Phase two: swap the master and slave roles and reinitialize→Exchange and compare again→Print the results of both phases
| Item | Source analysis |
|---|---|
| Function | Runs two phases on one pair of peripherals: in the first phase SPI1 is master and SPI2 is slave, in the second phase SPI1 is slave and SPI2 is master; both phases exchange 32 bytes in 8-bit full-duplex mode with software NSS and compare the results. |
| Source files | src/main.c: clock and pin configuration, both-phase initialization, the transfer loop and the result comparison;inc/main.h: the SPI_MASTER/SPI_SLAVE macros and the pin and buffer-length macros;readme.txt: function, environment and wiring notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Both phases use SPI_DIR_DOUBLELINE_FULLDUPLEX, SPI_DATA_SIZE_8BITS, SPI_NSS_SOFT, SPI_CLKPOL_LOW, SPI_CLKPHA_FIRST_EDGE, SPI_FB_LSB and SPI_BR_PRESCALER_8, with CRCPoly 7 and a buffer length of 32.Before the second phase both SPI peripherals are first reset with SPI_I2S_DeInit and then reinitialized, and GPIO_Configuration swaps the master and slave roles of the two pin groups. |
| Program and data path | main runs one transfer with the first-phase configuration and compares it with Buffercmp;then it swaps the master and slave roles, resets and reconfigures the peripherals, clears the buffers and indices and runs a second transfer, and finally prints the results of both phases. |
| Main APIs | SPI_InitStruct, SPI_Init, SPI_I2S_DeInit, SPI_Enable, SPI_I2S_TransmitData, SPI_I2S_ReceiveData, SPI_I2S_GetStatus |
| Verification | The UART prints SPI FullDuplex SoftNSS Demo, then Phase1: Master->Slave PASSED, Slave->Master PASSED and Phase2: Master->Slave PASSED, Slave->Master PASSED. Wire PA5, PA6 and PA7 of SPI1 to PB13, PB14 and PB15 of SPI2 respectively and connect a common ground. |
| Use note | With software NSS both sides may omit the NSS pin, but the SCK drive direction must still be swapped correctly; this example uses GPIO_Configuration in the second phase to make the SPI1 pins inputs and the SPI2 pins alternate-function outputs, and it must be called when the roles are swapped.SPI_I2S_DeInit followed by SPI_Init is required so the old configuration is cleared. |
Core-code walkthrough and porting
The example takes on two master/slave roles in turn on one pair of peripherals and completes 8-bit full-duplex transfer in both directions with software NSS.
Data path: GPIO_Configuration sets the master and slave pins → SPI_InitStruct/SPI_Init configure SPI1 and SPI2 → SPI_Enable → SPI_I2S_TransmitData/SPI_I2S_ReceiveData transfer → Buffercmp decides → repeat after swapping the roles
1. Initialize the first phase as software-NSS full-duplex. Master and slave share one structure, and the slave only changes SpiMode.
SPI_InitStruct(&SPI_InitStructure);
/* SPI_Master Config */
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_8;
SPI_InitStructure.FirstBit = SPI_FB_LSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI_MASTER, &SPI_InitStructure);
/* SPI_Slave Config */
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_Init(SPI_SLAVE, &SPI_InitStructure);
/* Enable SPI_Master */
SPI_Enable(SPI_MASTER, ENABLE);
/* Enable SPI_Slave */
SPI_Enable(SPI_SLAVE, ENABLE);
2. Transfer and compare in the first phase. Each byte is written to the slave transmit register first and then to the master transmit register, after which the received data of both sides is read back in turn and the two directions are compared.
/* Transfer procedure */
while (TxIdx < BufferSize)
{
/* Wait for SPI_Master Tx buffer empty */
while (SPI_I2S_GetStatus(SPI_MASTER, SPI_I2S_TE_FLAG) == RESET)
;
/* Send SPI_Slave data */
SPI_I2S_TransmitData(SPI_SLAVE, SPI_Slave_Buffer_Tx[TxIdx]);
/* Send SPI_Master data */
SPI_I2S_TransmitData(SPI_MASTER, SPI_Master_Buffer_Tx[TxIdx++]);
/* Wait for SPI_Slave data reception */
while (SPI_I2S_GetStatus(SPI_SLAVE, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read SPI_Slave received data */
SPI_Slave_Buffer_Rx[RxIdx] = SPI_I2S_ReceiveData(SPI_SLAVE);
/* Wait for SPI_Master data reception */
while (SPI_I2S_GetStatus(SPI_MASTER, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read SPI_Master received data */
SPI_Master_Buffer_Rx[RxIdx++] = SPI_I2S_ReceiveData(SPI_MASTER);
}
/* Check the correctness of written data */
TransferStatus1 = Buffercmp(SPI_Slave_Buffer_Rx, SPI_Master_Buffer_Tx, BufferSize);
TransferStatus2 = Buffercmp(SPI_Master_Buffer_Rx, SPI_Slave_Buffer_Tx, BufferSize);
3. Swap the roles and reinitialize in the second phase. GPIO_Configuration first swaps the pin directions, then both SPI peripherals are reset and reconfigured as slave and master in turn.
/* 2nd phase: SPI_Master as Slave and SPI_Slave as Master */
GPIO_Configuration(SPI_MODE_SLAVE, SPI_MODE_MASTER);
SPI_I2S_DeInit(SPI_MASTER);
SPI_I2S_DeInit(SPI_SLAVE);
SPI_InitStruct(&SPI_InitStructure);
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_8;
SPI_InitStructure.FirstBit = SPI_FB_LSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI_MASTER, &SPI_InitStructure);
/* SPI_Slave Re-configuration as Master */
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_Init(SPI_SLAVE, &SPI_InitStructure);
/* Enable SPI_Slave */
SPI_Enable(SPI_SLAVE, ENABLE);
/* Enable SPI_Master */
SPI_Enable(SPI_MASTER, ENABLE);
4. Transfer and compare in the second phase. SPI2 is now the master, so the loop first waits for the SPI2 transmit buffer to empty and then writes the master and slave data and reads back both receive buffers.
/* Transfer procedure */
while (TxIdx < BufferSize)
{
/* Wait for SPI_Slave Tx buffer empty */
while (SPI_I2S_GetStatus(SPI_SLAVE, SPI_I2S_TE_FLAG) == RESET)
;
/* Send SPI_Master data */
SPI_I2S_TransmitData(SPI_MASTER, SPI_Master_Buffer_Tx[TxIdx]);
/* Send SPI_Slave data */
SPI_I2S_TransmitData(SPI_SLAVE, SPI_Slave_Buffer_Tx[TxIdx++]);
/* Wait for SPI_Master data reception */
while (SPI_I2S_GetStatus(SPI_MASTER, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read SPI_Master received data */
SPI_Master_Buffer_Rx[RxIdx] = SPI_I2S_ReceiveData(SPI_MASTER);
/* Wait for SPI_Slave data reception */
while (SPI_I2S_GetStatus(SPI_SLAVE, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read SPI_Slave received data */
SPI_Slave_Buffer_Rx[RxIdx++] = SPI_I2S_ReceiveData(SPI_SLAVE);
}
Corresponding source files: src/main.c, inc/main.h
5.19.3.3 Simplex_Interrupt Single-line interrupt transfer
Example directory: projects/n32g41x_EVAL/examples/SPI/Simplex_Interrupt/
Initialize the clocks, the pin alternate functions, the interrupt priorities and the print UART→Configure SPI1 as a single-line transmit master and SPI2 as a single-line receive slave→Enable the master transmit-empty interrupt and the slave receive-not-empty interrupt→The interrupt handlers exchange one byte at a time→The main loop waits for completion and compares the result
| Item | Source analysis |
|---|---|
| Function | SPI1 acts as a single-line transmit master and sends one byte per transmit-empty interrupt, while SPI2 acts as a single-line receive slave and receives one byte per receive-not-empty interrupt; the main loop waits until the buffer is full and compares the data. |
| Source files | src/main.c: clock, GPIO and NVIC configuration, the transfer loop and the comparison;src/n32g41x_it.c: SPI1_IRQHandler and SPI2_IRQHandler;inc/main.h: the SPI_MASTER/SPI_SLAVE, interrupt-number and pin macros;readme.txt: function, environment and wiring notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | The master is SPI_DIR_SINGLELINE_TX and the slave is SPI_DIR_SINGLELINE_RX, both SPI_DATA_SIZE_8BITS, SPI_NSS_HARD, SPI_CLKPOL_HIGH, SPI_CLKPHA_FIRST_EDGE, SPI_FB_MSB and SPI_BR_PRESCALER_8;the master calls SPI_SSOutputEnable. The NVIC uses NVIC_PriorityGroup_1, with the SPI1 interrupt at preemption priority 1 and subpriority 2 and the SPI2 interrupt at preemption priority 0 and subpriority 1. |
| Program and data path | main configures the single-line master and slave, enables the transmit-empty and receive-not-empty interrupts and enters a wait loop;the master interrupt sends one byte per entry and disables the transmit interrupt when BufferSize is reached;the slave interrupt receives one byte per entry; once full, Buffercmp compares the receive buffer with the transmit buffer. |
| Main APIs | SPI_InitStruct, SPI_Init, SPI_SSOutputEnable, SPI_I2S_EnableInt, SPI_Enable, SPI_I2S_GetIntStatus, SPI_I2S_TransmitData, SPI_I2S_ReceiveData |
| Verification | The UART prints SPI Simplex Interrupt Demo, then Test PASSED! on success or Test FAILED! on failure.Wire the master MOSI (PA7) to the slave MISO (PB14) and the master NSS and SCK to the slave NSS and SCK respectively, and connect a common ground. |
| Use note | In single-line transmit the master transmit pin is tied to the slave receive pin, and the NSS and SCK of both sides must also be connected; the master needs SPI_SSOutputEnable to pull NSS low.Both interrupt handlers must stop their own interrupt once the buffer is full, otherwise they keep re-entering. |
Core-code walkthrough and porting
The example uses single-line mode along one data direction only, where the master is driven by the transmit-empty interrupt and the slave by the receive-not-empty interrupt.
Data path: the SPI_InitType configuration → SPI_Init → SPI_I2S_EnableInt enables the transmit-empty/receive-not-empty interrupts → the master SPI1_IRQHandler and the slave SPI2_IRQHandler exchange one byte at a time → the main loop compares with Buffercmp
1. Configure the single-line master and slave plus the interrupts. The master is set to single-line transmit and outputs NSS, the slave to single-line receive, and the transmit-empty and receive-not-empty interrupts are enabled on each side.
SPI_InitStruct(&SPI_InitStructure);
/* SPI_Master configuration: single line TX, master mode */
SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_TX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_HARD;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_8;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI_MASTER, &SPI_InitStructure);
/* SPI_Slave configuration: single line RX, slave mode */
SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_RX;
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_Init(SPI_SLAVE, &SPI_InitStructure);
/* Enable SPI_Master NSS output for master mode */
SPI_SSOutputEnable(SPI_MASTER, ENABLE);
/* Enable SPI_Master TXE interrupt */
SPI_I2S_EnableInt(SPI_MASTER, SPI_I2S_INT_TE, ENABLE);
/* Enable SPI_Slave RXNE interrupt */
SPI_I2S_EnableInt(SPI_SLAVE, SPI_I2S_INT_RNE, ENABLE);
/* Enable SPI_Slave */
SPI_Enable(SPI_SLAVE, ENABLE);
/* Enable SPI_Master */
SPI_Enable(SPI_MASTER, ENABLE);
2. Configure the priorities of the two interrupts. The slave receive interrupt has a higher preemption priority than the master transmit interrupt, so received data is taken away first.
NVIC_InitType NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* SPI_Master interrupt */
NVIC_InitStructure.NVIC_IRQChannel = SPI_MASTER_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* SPI_Slave interrupt */
NVIC_InitStructure.NVIC_IRQChannel = SPI_SLAVE_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
3. Exchange one byte at a time in the interrupt handlers. The master handler uses SPI_I2S_GetIntStatus to test the transmit-empty flag and disables the transmit interrupt once BufferSize is reached; the slave handler tests the receive-not-empty flag and reads the data away.
void SPI1_IRQHandler(void)
{
if (SPI_I2S_GetIntStatus(SPI_MASTER, SPI_I2S_INT_TE) != RESET)
{
SPI_I2S_TransmitData(SPI_MASTER, SPI_Master_Buffer_Tx[TxIdx++]);
if (TxIdx == BufferSize)
{
SPI_I2S_EnableInt(SPI_MASTER, SPI_I2S_INT_TE, DISABLE);
}
}
}
void SPI2_IRQHandler(void)
{
if (SPI_I2S_GetIntStatus(SPI_SLAVE, SPI_I2S_INT_RNE) != RESET)
{
SPI_Slave_Buffer_Rx[RxIdx++] = SPI_I2S_ReceiveData(SPI_SLAVE);
}
}
4. Wait in the main loop for the buffer to fill and compare the result. The main loop only waits for RxIdx to reach BufferSize and then compares the slave receive buffer with the master transmit buffer.
/* Wait for transfer to complete */
while (RxIdx < BufferSize)
{
}
TransferStatus = Buffercmp(SPI_Slave_Buffer_Rx, SPI_Master_Buffer_Tx, BufferSize);
if (TransferStatus == PASSED)
{
log_info("Test PASSED!\r\n");
}
else
{
log_info("Test FAILED!\r\n");
}
Corresponding source files: src/main.c, src/n32g41x_it.c, inc/main.h
5.19.3.4 SPI_9Bit 9-bit data frames
Example directory: projects/n32g41x_EVAL/examples/SPI/SPI_9Bit/
Initialize the clocks, the pin alternate functions and the print UART→The master delays until the slave is ready→Both SPI2 sides are configured as 9-bit full-duplex with software NSS→The slave pre-loads the first byte and exchanges on receive-not-empty→The master exchanges and compares only the low 9 bits→Light the LED on success
| Item | Source analysis |
|---|---|
| Function | The SPI_9Bit_Master and SPI_9Bit_Slave sub-projects are flashed to two boards respectively, and SPI2 exchanges 32 data words in 9-bit frame, software-NSS full-duplex mode;the master sends from 0x101 and expects from 0x151, the slave sends from 0x151 and expects from 0x101, and only the low 9 bits are compared. |
| Source files | SPI_9Bit_Master/src/main.c and SPI_9Bit_Slave/src/main.c: each side’s master/slave initialization, transfer and comparison;both sub-projects’ inc/main.h: the SPI2 pin and expected-data macros;readme.txt: function, environment and wiring notes;each sub-project’s MDK-ARM/, EWARM/. |
| Key configuration | Both sides use SPI_DATA_SIZE_9BITS, SPI_DIR_DOUBLELINE_FULLDUPLEX, SPI_NSS_SOFT, SPI_CLKPOL_LOW, SPI_CLKPHA_FIRST_EDGE, SPI_FB_MSB and SPI_BR_PRESCALER_64, with a buffer length of 32;only SPI2 supports 9-bit frames and they do not use CRC. The master delays 1 s after power-up to let the slave get ready. |
| Program and data path | The master enters its transmit loop: it waits for the transmit buffer to empty, writes the 9-bit data, then waits for receive-not-empty and reads it back; after the loop a Buffercmp that compares only the low 9 bits checks the data against the expected buffer.The slave pre-loads the first byte before enabling SPI, then reads each received byte and loads the next one, and finally compares the low 9 bits in the same way. |
| Main APIs | SPI_InitStruct, SPI_Init, SPI_Enable, SPI_I2S_TransmitData, SPI_I2S_ReceiveData, SPI_I2S_GetStatus |
| Verification | The master UART prints SPI2 9-Bit Master Demo and the slave SPI2 9-Bit Slave Demo;on success each prints 9-Bit Transfer PASSED! and lights LED1.Connect the like-named pins of the two boards (PB13 to PB13, PB14 to PB14, PB15 to PB15) and a common ground. |
| Use note | Only SPI2 supports 9-bit frames, while SPI1 supports only 8-bit and 16-bit; 9-bit frames do not support CRC. The received 9-bit data occupies only DAT[8:0] with the upper bits at 0, so the comparison must mask the low 9 bits.Flash the slave first and then the master, and the 1 s master delay exists to let the slave get ready. |
Core-code walkthrough and porting
The paired sub-projects run the same 9-bit protocol on two boards and both compare only the low 9 bits of the received data.
Data path: the master main configures and delays → SPI_I2S_TransmitData/SPI_I2S_ReceiveData transfer → low-9-bit Buffercmp; the slave main configures → pre-loads the first byte → reads on receive-not-empty and loads the next → low-9-bit Buffercmp
1. The master is configured as 9-bit full-duplex and transfers. The structure sets SPI_DATA_SIZE_9BITS; the loop waits for the transmit buffer to empty before writing and then waits for receive-not-empty to read back.
/* SPI2 configuration: Master, 9-bit, soft NSS, no CRC (9-bit does not support CRC) */
SPI_InitStruct(&SPI_InitStructure);
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_9BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_64;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI2, &SPI_InitStructure);
/* Enable SPI2 */
SPI_Enable(SPI2, ENABLE);
log_info("Master ready, start transfer...\r\n");
/* Transfer procedure */
while (TxIdx < BufferSize)
{
/* Wait for Tx buffer empty */
while (SPI_I2S_GetStatus(SPI2, SPI_I2S_TE_FLAG) == RESET)
;
/* Send 9-bit data */
SPI_I2S_TransmitData(SPI2, SPI_Master_Tx[TxIdx++]);
/* Wait for Rx buffer not empty */
while (SPI_I2S_GetStatus(SPI2, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read received 9-bit data (DAT[8:0], DAT[15:9] forced to 0) */
SPI_Master_Rx[RxIdx++] = SPI_I2S_ReceiveData(SPI2);
}
2. The slave pre-loads the first byte and exchanges on receive-not-empty. The slave writes one byte to the transmit register before enabling SPI, and in the loop it reads each received byte and loads the next one if data remains.
/* SPI2 configuration: Slave, 9-bit, soft NSS, no CRC (9-bit does not support CRC) */
SPI_InitStruct(&SPI_InitStructure);
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_9BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_64;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI2, &SPI_InitStructure);
/* Enable SPI2 */
SPI_Enable(SPI2, ENABLE);
log_info("Slave ready, waiting for master...\r\n");
/* Pre-load first TX data before enabling SPI */
SPI_I2S_TransmitData(SPI2, SPI_Slave_Tx[TxIdx++]);
/* Transfer procedure */
while (RxIdx < BufferSize)
{
/* Wait for Rx buffer not empty (master has sent a byte) */
while (SPI_I2S_GetStatus(SPI2, SPI_I2S_RNE_FLAG) == RESET)
;
/* Read received 9-bit data */
SPI_Slave_Rx[RxIdx++] = SPI_I2S_ReceiveData(SPI2);
/* Load next TX data if available */
if (TxIdx < BufferSize)
{
/* Wait for Tx buffer empty */
while (SPI_I2S_GetStatus(SPI2, SPI_I2S_TE_FLAG) == RESET)
;
SPI_I2S_TransmitData(SPI2, SPI_Slave_Tx[TxIdx++]);
}
}
3. The master delays until the slave is ready. In a board-to-board test the slave takes longer to come up after power-on, so the master delays 1 s before configuring its pins.
log_init();
log_info("\r\nSPI2 9-Bit Master Demo\r\n");
/* Delay 1s for Slave to be ready when doing board-to-board test */
SysTick_Delay_Ms(1000);
4. Compare only the low 9 bits and light the LED on success. Buffercmp masks each element with 0x01FF to compare the low 9 bits, avoiding the difference of the upper bits being 0 in 9-bit frames; on success it lights LED1.
TestStatus Buffercmp(uint16_t* pBuffer1, uint16_t* pBuffer2, uint16_t BufferLength)
{
while (BufferLength--)
{
if ((*pBuffer1 & 0x01FF) != (*pBuffer2 & 0x01FF))
{
return FAILED;
}
pBuffer1++;
pBuffer2++;
}
return PASSED;
}
if (TransferStatus == PASSED)
{
log_info("9-Bit Transfer PASSED!\r\n");
/* LED1 ON: test passed */
GPIO_ResetBits(LED1_PORT, LED1_PIN);
}
Corresponding source files: SPI_9Bit_Master/src/main.c, SPI_9Bit_Slave/src/main.c, SPI_9Bit_Master/inc/main.h
5.19.3.5 SPI_DMA_T&R Single-line DMA transfer
Example directory: projects/n32g41x_EVAL/examples/SPI/SPI_DMA_T&R/
Initialize the clocks, the DMA and the SPI pin alternate functions→Configure the slave receive and master transmit DMA channels and their remapping→Configure SPI1 as a single-line transmit master and SPI2 as a single-line receive slave→Enable the SPI DMA requests and the two DMA channels→Wait for completion and compare the result
| Item | Source analysis |
|---|---|
| Function | SPI1 acts as a single-line transmit master and SPI2 as a single-line receive slave; the master transmit buffer is moved into the SPI1 data register by a DMA channel and the slave receive is moved out to memory by a DMA channel, and the data is compared once the transfer completes. |
| Source files | src/main.c: clock, GPIO and DMA configuration, master/slave initialization, DMA enabling and the comparison;inc/main.h: the SPI_MASTER/SPI_SLAVE, DMA channel and remapping, pin and buffer-length macros;readme.txt: function, environment and wiring notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | The master is SPI_DIR_SINGLELINE_TX and the slave is SPI_DIR_SINGLELINE_RX, both SPI_DATA_SIZE_8BITS, SPI_NSS_HARD, SPI_CLKPOL_HIGH, SPI_CLKPHA_FIRST_EDGE, SPI_FB_MSB and SPI_BR_PRESCALER_8;the slave receive uses one DMA channel (peripheral as source, memory incrementing) and the master transmit another (peripheral as destination); both perform the SPI1 TX and SPI2 RX DMA request remapping. |
| Program and data path | main first initializes the two DMA channels and remaps them, then configures the single-line master and slave and calls SPI_SSOutputEnable and SPI_I2S_EnableDma, then enables both DMA channels, waits for both transfer-complete flags and uses Buffercmp to compare the slave receive buffer with the master transmit buffer. |
| Main APIs | DMA_Init, DMA_RequestRemap, SPI_InitStruct, SPI_Init, SPI_SSOutputEnable, SPI_I2S_EnableDma, SPI_Enable |
| Verification | The UART prints This is SPI DMA demo!!, then Test PASS!! on success or Test fail!! on failure.Wire the master SCK (PA5) to the slave SCK (PB13), the master MOSI (PA7) to the slave MISO (PB14) and the master NSS (PA4) to the slave NSS (PB12), and connect a common ground. |
| Use note | In single-line transmit the master MOSI is tied to the slave MISO; the master needs SPI_SSOutputEnable to output NSS.The DMA enabling must follow the order of the example around the SPI enabling, and the transfer only starts once both SPI_I2S_EnableDma and DMA_EnableChannel are ready. |
Core-code walkthrough and porting
The example hands the single-line SPI transmit and receive each to a DMA channel, leaving the CPU to configure and wait for completion.
Data path: DMA_Init/DMA_RequestRemap configure the two channels → SPI_Init configures the single-line master and slave → SPI_I2S_EnableDma → DMA_EnableChannel → DMA and SPI move the data → DMA_GetFlagStatus waits for completion → Buffercmp compares
1. Configure the slave receive and master transmit DMA channels. The slave receive direction has the peripheral as source and increments memory; the master transmit direction has the peripheral as destination; the two channels perform the SPI2 RX and SPI1 TX remapping respectively.
/* SPI_SLAVE Rx DMA Channel configuration */
DMA_DeInit(SPI_SLAVE_Rx_DMA_Channel);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_SLAVE->DAT;
DMA_InitStructure.MemAddr = (uint32_t)SPI_SLAVE_Buffer_Rx;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
DMA_InitStructure.BufSize = BufferSize;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(SPI_SLAVE_Rx_DMA_Channel, &DMA_InitStructure);
DMA_RequestRemap(SPI_SLAVE_Rx_DMA_Remap, DMA, SPI_SLAVE_Rx_DMA_Channel, ENABLE);
/* SPI_MASTER Tx DMA Channel configuration */
DMA_DeInit(SPI_MASTER_Tx_DMA_Channel);
DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_MASTER->DAT;
DMA_InitStructure.MemAddr = (uint32_t)SPI_MASTER_Buffer_Tx;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_Init(SPI_MASTER_Tx_DMA_Channel, &DMA_InitStructure);
DMA_RequestRemap(SPI_MASTER_Tx_DMA_Remap, DMA, SPI_MASTER_Tx_DMA_Channel, ENABLE);
2. Configure the single-line master and slave and open the SPI DMA requests. The master is set to single-line transmit and outputs NSS, the slave to single-line receive, and the slave receive and master transmit DMA requests are then enabled.
/* SPI_MASTER configuration: single-line TX, master mode */
SPI_InitStruct(&SPI_InitStructure);
SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_TX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_HARD;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_8;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI_MASTER, &SPI_InitStructure);
/* SPI_SLAVE configuration: single-line RX, slave mode */
SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_RX;
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_Init(SPI_SLAVE, &SPI_InitStructure);
/* Enable NSS output for master */
SPI_SSOutputEnable(SPI_MASTER, ENABLE);
/* Enable SPI DMA requests */
SPI_I2S_EnableDma(SPI_SLAVE, SPI_I2S_DMA_RX, ENABLE);
SPI_I2S_EnableDma(SPI_MASTER, SPI_I2S_DMA_TX, ENABLE);
/* Enable SPI peripherals */
SPI_Enable(SPI_SLAVE, ENABLE);
SPI_Enable(SPI_MASTER, ENABLE);
3. Enable the DMA channels and wait for completion. Once both channels are enabled, the code waits for both transfer-complete flags and then compares the slave receive buffer with the master transmit buffer.
/* Enable DMA channels */
DMA_EnableChannel(SPI_SLAVE_Rx_DMA_Channel, ENABLE);
DMA_EnableChannel(SPI_MASTER_Tx_DMA_Channel, ENABLE);
/* Wait for DMA transfer complete */
while (!DMA_GetFlagStatus(SPI_MASTER_Tx_DMA_FLAG, DMA))
;
while (!DMA_GetFlagStatus(SPI_SLAVE_Rx_DMA_FLAG, DMA))
;
/* Verify transferred data */
TransferStatus = Buffercmp(SPI_SLAVE_Buffer_Rx, SPI_MASTER_Buffer_Tx, BufferSize);
Corresponding source files: src/main.c, inc/main.h
5.19.3.6 SPI_FLASH Polling access to external FLASH
Example directory: projects/n32g41x_EVAL/examples/SPI/SPI_FLASH/
Initialize the SPI1 master and the chip-select GPIO→Read the FLASH device ID→Erase a sector→Write a block of data by polling and read it back→Compare the written and read-back data→Erase the sector again and confirm it is all 0xFF
| Item | Source analysis |
|---|---|
| Function | SPI1 acts as a full-duplex master and drives an external W25Q128 serial FLASH over SPI by polling: it reads the device ID, erases a sector, writes a block of data and reads it back for comparison, then erases again and confirms the data has become 0xFF. |
| Source files | src/main.c: the main flow and Buffercmp;src/spi_flash.c: the driver functions sFLASH_Init, sFLASH_SendByte, sFLASH_ReadID, erase and read/write;inc/spi_flash.h: the SPI pins, command codes and function declarations;inc/main.h: the address and buffer macros;readme.txt: function, environment and wiring notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | sFLASH_Init configures SPI1 as SPI_DIR_DOUBLELINE_FULLDUPLEX, SPI_MODE_MASTER, SPI_DATA_SIZE_8BITS, SPI_CLKPOL_HIGH, SPI_CLKPHA_SECOND_EDGE, SPI_NSS_SOFT, SPI_BR_PRESCALER_16 and SPI_FB_MSB;chip select PA4 is an ordinary GPIO driven by sFLASH_CS_LOW/sFLASH_CS_HIGH;SCK, MISO and MOSI are PA5, PA6 and PA7. |
| Program and data path | main first calls sFLASH_Init and then sFLASH_ReadID;once the ID is confirmed to be the W25Q128 it erases a sector, calls sFLASH_WriteBuffer to write a text buffer and sFLASH_ReadBuffer to read it back, comparing with Buffercmp;then it erases again and checks byte by byte that the read-back value is 0xFF. |
| Main APIs | SPI_InitStruct, SPI_Init, SPI_Enable, SPI_I2S_TransmitData, SPI_I2S_ReceiveData, SPI_I2S_GetStatus |
| Verification | The UART prints This is SPI FLASH demo!! and the ID read, then Write data to flash OK., SPI write and read flash OK. and Sector erase OK.. The evaluation board carries no FLASH, so an external W25Q128 must be connected with SCK, MISO, MOSI and CS wired to PA5, PA6, PA7 and PA4 in turn. |
| Use note | After transmitting, sFLASH_SendByte must wait for transmit-empty, then for BUSY to clear and then for receive-not-empty before reading back, and the polling order cannot be changed or stale data is read.The W25Q128 has a multi-byte ID and the example accepts both 0x00EF4018 and 0x00EF7018. The target sector must be erased before writing. |
Core-code walkthrough and porting
The example drives an external serial FLASH by polling with SPI1 as master, completing an erase-write-read-verify flow.
Data path: sFLASH_Init configures SPI1 → sFLASH_ReadID confirms the device → sFLASH_EraseSector → sFLASH_WriteBuffer/sFLASH_ReadBuffer transfer through sFLASH_SendByte → Buffercmp compares
1. Initialize the SPI1 master. The driver configures SPI1 as a full-duplex master in mode 3 (CLKPOL_HIGH plus CLKPHA_SECOND_EDGE), with chip select on a separate GPIO.
void sFLASH_Init(void)
{
SPI_InitType SPI_InitStructure;
sFLASH_LowLevel_Init();
sFLASH_CS_HIGH();
SPI_InitStruct(&SPI_InitStructure);
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_SECOND_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_16;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(sFLASH_SPI, &SPI_InitStructure);
SPI_Enable(sFLASH_SPI, ENABLE);
}
2. Transfer one byte by polling. Each send first waits for transmit-empty, and after writing it waits for transmit-empty again and for BUSY to clear, and finally waits for receive-not-empty and reads back, so the value read belongs to the current exchange.
uint8_t sFLASH_SendByte(uint8_t byte)
{
while (SPI_I2S_GetStatus(sFLASH_SPI, SPI_I2S_TE_FLAG) == RESET)
;
SPI_I2S_TransmitData(sFLASH_SPI, byte);
while (SPI_I2S_GetStatus(sFLASH_SPI, SPI_I2S_TE_FLAG) == RESET)
;
while (SPI_I2S_GetStatus(sFLASH_SPI, SPI_I2S_BUSY_FLAG) != RESET)
;
while (SPI_I2S_GetStatus(sFLASH_SPI, SPI_I2S_RNE_FLAG) == RESET)
;
return (uint8_t)SPI_I2S_ReceiveData(sFLASH_SPI);
}
3. Read the device ID and run the full erase-write-read flow. The main flow first reads the ID to identify the device, then erases a sector, writes and reads back the data and compares the result, and finally erases again and confirms it reads 0xFF.
/* Initialize the SPI FLASH driver */
sFLASH_Init();
/* Get SPI Flash ID */
FlashID = sFLASH_ReadID();
log_info("Read ID=0x%06x\r\n", FlashID);
/* Get the length of Tx_Buffer */
Tx_Buffer_Size = sizeof(Tx_Buffer);
/* Check the SPI Flash ID */
if ((FlashID == sFLASH_ID) || (FlashID == sFLASH_W25Q128_ID_DTR))
{
/* Erase SPI FLASH Sector to write on */
sFLASH_EraseSector(FLASH_SectorToErase);
/* Write Tx_Buffer data to SPI FLASH memory */
sFLASH_WriteBuffer(Tx_Buffer, FLASH_WriteAddress, Tx_Buffer_Size);
log_info("Write data to flash OK.\r\n");
/* Read data from SPI FLASH memory */
sFLASH_ReadBuffer(Rx_Buffer, FLASH_ReadAddress, Tx_Buffer_Size);
/* Check the correctness of written data */
TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, Tx_Buffer_Size);
if (PASSED == TransferStatus1)
log_info("SPI write and read flash OK.\r\n");
else
log_info("SPI write and read flash fail.\r\n");
/* Erase SPI FLASH Sector */
sFLASH_EraseSector(FLASH_SectorToErase);
/* Read data from SPI FLASH memory */
sFLASH_ReadBuffer(Rx_Buffer, FLASH_ReadAddress, Tx_Buffer_Size);
/* Check the correctness of erasing operation */
for (Index = 0; Index < Tx_Buffer_Size; Index++)
{
if (Rx_Buffer[Index] != 0xFF)
{
TransferStatus2 = FAILED;
break;
}
}
4. The read-ID command sequence. sFLASH_ReadID pulls chip select low, sends 0x9F and then reads three bytes that are assembled into the 24-bit ID.
uint32_t sFLASH_ReadID(void)
{
uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
sFLASH_CS_LOW();
sFLASH_SendByte(0x9F);
Temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
Temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
Temp2 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
sFLASH_CS_HIGH();
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}
Corresponding source files: src/main.c, src/spi_flash.c, inc/spi_flash.h, inc/main.h
5.19.3.7 SPI_FLASH_DMA DMA access to external FLASH
Example directory: projects/n32g41x_EVAL/examples/SPI/SPI_FLASH_DMA/
Initialize the SPI1 master and the chip-select GPIO→Read the FLASH device ID→Erase a sector→Switch to single-line transmit and move the write data by DMA→Switch to read-only two-line and move the read data by DMA→Compare the write and read data and confirm the erase result
| Item | Source analysis |
|---|---|
| Function | SPI1 acts as master and drives an external W25Q128 serial FLASH by DMA: a page write switches SPI to single-line transmit and a read to read-only two-line, with the data moved by the transmit and receive DMA; at the end it compares the write and read data and confirms the erase leaves everything 0xFF. |
| Source files | src/main.c: the main flow and Buffercmp;src/spi_flash.c: the driver functions sFLASH_Init, sFLASH_DMA_PageWrite, sFLASH_DMA_ReadBuffer, sFLASH_DMA_WriteBuffer;inc/spi_flash.h: the SPI pins, DMA channels, command codes and function declarations;readme.txt: function, environment and wiring notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | sFLASH_Init configures SPI1 as a full-duplex master with SPI_DATA_SIZE_8BITS, SPI_CLKPOL_HIGH, SPI_CLKPHA_SECOND_EDGE, SPI_NSS_SOFT and SPI_BR_PRESCALER_8;a page write switches to SPI_DIR_SINGLELINE_TX and SPI_BR_PRESCALER_4 and a read to SPI_DIR_DOUBLELINE_RONLY and SPI_BR_PRESCALER_4, restoring full-duplex afterwards.The receive uses DMA_CH2 and the transmit DMA_CH3. SCK, MISO, MOSI and CS are PA1, PA0, PA2 and PA4. |
| Program and data path | main first calls sFLASH_Init and sFLASH_ReadID;once the ID is confirmed it erases a sector, calls sFLASH_DMA_WriteBuffer to write page by page by DMA and sFLASH_DMA_ReadBuffer to read back by DMA and compares with Buffercmp;then it erases again and reads back by DMA to confirm everything is 0xFF. |
| Main APIs | SPI_InitStruct, SPI_Init, SPI_Enable, SPI_I2S_EnableDma, DMA_Init, DMA_EnableChannel, SPI_I2S_TransmitData, SPI_I2S_ReceiveData |
| Verification | The UART prints the ID read, then spi dma write and read flash OK. and Sector erase OK.. The evaluation board carries no FLASH, so an external W25Q128 must be connected with SCK, MISO, MOSI and CS wired to PA1, PA0, PA2 and PA4 in turn, and the LED1 jumper sharing PA1 must be removed. |
| Use note | This example runs at HSE+PLL 72 MHz with SPI up to 36 MHz, unlike the clock configuration of the other examples. Before a DMA write it switches SPI to single-line transmit and before a read to read-only two-line, and it must restore the full configuration afterwards, otherwise the mode of later byte transfers is wrong. When switching modes SPI must be disabled and enabled again so the direction field can be written. |
Core-code walkthrough and porting
The example hands the FLASH data movement to DMA on top of polling access and temporarily switches the SPI working mode for the write and read stages.
Data path: sFLASH_Init configures SPI1 → sFLASH_DMA_PageWrite switches to single-line transmit → DMA_EnableChannel moves the data → restore full-duplex → sFLASH_DMA_ReadBuffer switches to read-only two-line → DMA moves the data → Buffercmp compares
1. The page-write stage switches to single-line transmit and starts DMA. The function first sends the write command, switches SPI to single-line transmit and moves the whole page with the transmit DMA, and once the transfer completes it disables the channel and raises chip select.
sFLASH_DMA_Init(DMA_SEND, (uint32_t)pBuffer, NumByteToWrite);
DMA_EnableChannel(sFLASH_SPI_Tx_DMA_Channel, ENABLE);
SPI_I2S_EnableDma(sFLASH_SPI, SPI_I2S_DMA_TX, ENABLE);
SPI_Enable(sFLASH_SPI, ENABLE);
/* Wait for DMA transfer complete */
while (!DMA_GetFlagStatus(sFLASH_SPI_Tx_DMA_FLAG, DMA))
;
/* Wait for SPI transfer complete */
while (SPI_I2S_GetStatus(sFLASH_SPI, SPI_I2S_BUSY_FLAG))
;
SPI_I2S_EnableDma(sFLASH_SPI, SPI_I2S_DMA_TX, DISABLE);
DMA_EnableChannel(sFLASH_SPI_Tx_DMA_Channel, DISABLE);
sFLASH_CS_HIGH();
2. After the read command it switches to read-only two-line and starts the receive DMA. It pulls chip select low, sends the read command and the 24-bit address, then switches SPI to read-only two-line and moves the given length with the receive DMA.
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_CMD_READ);
sFLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
sFLASH_SendByte((ReadAddr & 0xFF00) >> 8);
sFLASH_SendByte(ReadAddr & 0xFF);
/* Switch to read-only mode for DMA read */
SPI_Enable(sFLASH_SPI, DISABLE);
SPI_I2S_DeInit(sFLASH_SPI);
sFLASH_DMA_Init(DMA_RECV, (uint32_t)pBuffer, NumByteToRead);
DMA_EnableChannel(sFLASH_SPI_Rx_DMA_Channel, ENABLE);
SPI_I2S_EnableDma(sFLASH_SPI, SPI_I2S_DMA_RX, ENABLE);
SPI_Enable(sFLASH_SPI, ENABLE);
/* Wait for DMA transfer complete */
while (!DMA_GetFlagStatus(sFLASH_SPI_Rx_DMA_FLAG, DMA))
;
SPI_I2S_EnableDma(sFLASH_SPI, SPI_I2S_DMA_RX, DISABLE);
DMA_EnableChannel(sFLASH_SPI_Rx_DMA_Channel, DISABLE);
3. The main program completes erase, write and read by DMA. The main flow reads the ID, erases a sector, writes by DMA, reads back by DMA and compares, and finally erases again and reads back to confirm everything is 0xFF.
/* Initialize the SPI FLASH driver */
sFLASH_Init();
/* Get SPI Flash ID */
FlashID = sFLASH_ReadID();
printf("Read ID=0x%06x\r\n", FlashID);
for (i = 0; i < 512; i++)
{
Tx_Buffer[i] = i + 1;
}
/* Check the SPI Flash ID */
if ((FlashID == sFLASH_ID) || (FlashID == sFLASH_W25Q128_ID_DTR))
{
/* Erase SPI FLASH Sector to write on */
sFLASH_EraseSector(FLASH_SectorToErase);
/* Write Tx_Buffer data to SPI FLASH memory by DMA */
sFLASH_DMA_WriteBuffer(Tx_Buffer, FLASH_WriteAddress, BufferSize);
/* Read data from SPI FLASH memory by DMA */
sFLASH_DMA_ReadBuffer(Rx_Buffer, FLASH_ReadAddress, BufferSize);
/* Check the correctness of written data */
TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
4. The pins and DMA channels are defined by macros. The header collects the SPI instance, the DMA channels, the remapping and every pin into macros, so porting changes only that one place.
/* DMA configuration: use DMA_CH2 for RX, DMA_CH3 for TX */
#define sFLASH_SPI_DMA_CLK RCC_AHB_PERIPH_DMA
#define sFLASH_SPI_Rx_DMA_Channel DMA_CH2
#define sFLASH_SPI_Rx_DMA_FLAG DMA_FLAG_TC2
#define sFLASH_SPI_Tx_DMA_Channel DMA_CH3
#define sFLASH_SPI_Tx_DMA_FLAG DMA_FLAG_TC3
#define sFLASH_SPI_Rx_DMA_Remap DMA_REMAP_SPI1_RX
#define sFLASH_SPI_Tx_DMA_Remap DMA_REMAP_SPI1_TX
/* SPI1 pin definition: PA1(SCK), PA0(MISO), PA2(MOSI), PA4(CS as GPIO) */
#define sFLASH_SPI SPI1
#define sFLASH_SPI_CLK RCC_APB2_PERIPH_SPI1
#define sFLASH_SPI_SCK_PIN GPIO_PIN_1
#define sFLASH_SPI_SCK_GPIO_PORT GPIOA
#define sFLASH_SPI_MISO_PIN GPIO_PIN_0
#define sFLASH_SPI_MISO_GPIO_PORT GPIOA
#define sFLASH_SPI_MOSI_PIN GPIO_PIN_2
#define sFLASH_SPI_MOSI_GPIO_PORT GPIOA
#define sFLASH_CS_PIN GPIO_PIN_4
#define sFLASH_CS_GPIO_PORT GPIOA
Corresponding source files: src/main.c, src/spi_flash.c, inc/spi_flash.h
5.19.3.8 SPI_Simplex_DMA_Recv Single-line DMA receive
Example directory: projects/n32g41x_EVAL/examples/SPI/SPI_Simplex_DMA_Recv/
Initialize the clocks, the DMA and the SPI pin alternate functions→Configure the slave receive DMA channel and its remapping→Configure SPI1 as a single-line transmit master and SPI2 as a single-line receive slave→The master sends byte by byte by polling→The slave receives by DMA→Wait for completion and compare the result
| Item | Source analysis |
|---|---|
| Function | SPI1 acts as a single-line transmit master and sends byte by byte by polling, while SPI2 acts as a single-line receive slave whose received data is moved into memory by a DMA channel; the data is compared once the transfer completes. |
| Source files | src/main.c: clock, GPIO and DMA configuration, master/slave initialization, polling send and the comparison;inc/main.h: the SPI_MASTER/SPI_SLAVE, DMA channel and remapping, pin and buffer-length macros;readme.txt: function, environment and wiring notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | The master is SPI_DIR_SINGLELINE_TX and the slave is SPI_DIR_SINGLELINE_RX, both SPI_DATA_SIZE_8BITS, SPI_NSS_HARD, SPI_CLKPOL_HIGH, SPI_CLKPHA_FIRST_EDGE, SPI_FB_MSB and SPI_BR_PRESCALER_8;the slave receive uses one DMA channel (peripheral as source, memory incrementing) with the SPI2 RX remapping. |
| Program and data path | main first initializes the slave receive DMA channel and remaps it, then configures the single-line master and slave and calls SPI_SSOutputEnable and the slave SPI_I2S_EnableDma;after enabling the slave DMA channel the main loop sends the whole block by polling, and once the channel transfer-complete flag is seen Buffercmp compares the slave receive buffer with the master transmit buffer. |
| Main APIs | DMA_Init, DMA_RequestRemap, SPI_InitStruct, SPI_Init, SPI_SSOutputEnable, SPI_I2S_EnableDma, SPI_Enable, SPI_I2S_TransmitData |
| Verification | The UART prints This is SPI Simplex DMA Recv demo!!, then Test PASS!! on success or Test fail!! on failure.Wire the master SCK (PA5) to the slave SCK (PB13), the master MOSI (PA7) to the slave MISO (PB14) and the master NSS (PA4) to the slave NSS (PB12), and connect a common ground. |
| Use note | Only the slave receive uses DMA here, while the master still transmits by polling, so the master send rate must stay below the slave DMA transfer capability. In single-line transmit the master MOSI is tied to the slave MISO; the master needs SPI_SSOutputEnable to output NSS. |
Core-code walkthrough and porting
The example hands the single-line receive movement to DMA while the transmit side is still driven byte by byte by the master by polling.
Data path: DMA_Init/DMA_RequestRemap configure the slave receive channel → SPI_Init configures the single-line master and slave → SPI_I2S_EnableDma/DMA_EnableChannel → the master polls SPI_I2S_TransmitData → DMA moves the data into memory → Buffercmp compares
1. Configure the slave receive DMA channel. The slave receive direction has the peripheral as source and increments memory and performs the SPI2 RX remapping.
SPI_InitStruct(&SPI_InitStructure);
/* SPI_SLAVE_Rx_DMA_Channel configuration */
DMA_DeInit(SPI_SLAVE_Rx_DMA_Channel);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_SLAVE->DAT;
DMA_InitStructure.MemAddr = (uint32_t)SPI_SLAVE_Buffer_Rx;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
DMA_InitStructure.BufSize = BufferSize;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(SPI_SLAVE_Rx_DMA_Channel, &DMA_InitStructure);
DMA_RequestRemap(SPI_SLAVE_Rx_DMA_Remap, DMA, SPI_SLAVE_Rx_DMA_Channel, ENABLE);
2. Configure the single-line master and slave and enable the slave DMA receive. The master is set to single-line transmit and outputs NSS, the slave to single-line receive with the receive DMA request opened, and the slave DMA channel is then enabled.
/* SPI_MASTER configuration: single-line TX, master mode */
SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_TX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;
SPI_InitStructure.NSS = SPI_NSS_HARD;
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_8;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
SPI_InitStructure.CRCPoly = 7;
SPI_Init(SPI_MASTER, &SPI_InitStructure);
/* SPI_SLAVE configuration: single-line RX, slave mode */
SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_RX;
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_Init(SPI_SLAVE, &SPI_InitStructure);
/* Enable NSS output for master */
SPI_SSOutputEnable(SPI_MASTER, ENABLE);
/* Enable SPI_SLAVE Rx DMA request */
SPI_I2S_EnableDma(SPI_SLAVE, SPI_I2S_DMA_RX, ENABLE);
/* Enable SPI peripherals */
SPI_Enable(SPI_SLAVE, ENABLE);
SPI_Enable(SPI_MASTER, ENABLE);
/* Enable DMA channel */
DMA_EnableChannel(SPI_SLAVE_Rx_DMA_Channel, ENABLE);
3. The master sends by polling and waits for DMA completion. The main loop waits for the transmit buffer to empty before writing one byte, and after all data is sent it waits for the slave receive channel to complete before comparing.
/* Transfer procedure: master sends data by polling */
while (TxIdx < BufferSize)
{
while (SPI_I2S_GetStatus(SPI_MASTER, SPI_I2S_TE_FLAG) == RESET)
;
SPI_I2S_TransmitData(SPI_MASTER, SPI_MASTER_Buffer_Tx[TxIdx++]);
}
/* Wait for DMA transfer complete */
while (!DMA_GetFlagStatus(SPI_SLAVE_Rx_DMA_FLAG, DMA))
;
/* Verify transferred data */
TransferStatus = Buffercmp(SPI_SLAVE_Buffer_Rx, SPI_MASTER_Buffer_Tx, BufferSize);
Corresponding source files: src/main.c, inc/main.h
5.19.4 Notes and troubleshooting
| Scenario | Check & action | Source |
|---|---|---|
| Scope of 9-bit data frames | Only SPI2 supports 9-bit data frames, while SPI1 supports only 8-bit and 16-bit; the frame length must be decided before enabling SPI, because the DATFF field of SPI_CTRL1 can be written only while SPIEN=0. |
User manual |
| 9-bit frames and CRC are mutually exclusive | When the DATFF9 bit of SPI_CTRL2 is 1 the frame is fixed at 9 bits regardless of the DATFF field of SPI_CTRL1, and 9-bit frames do not support CRC. |
User manual |
| Bits that must not change during communication | Do not modify the LSBFF, MSEL, CLKPHA, CLKPOL and BR bits of SPI_CTRL1 while communication is in progress;disable SPI first if a change is needed. |
User manual |
| Master and slave clock modes must match | Whichever timing mode is chosen, the CLKPOL and CLKPHA configuration of the master and the slave must be identical, otherwise the exchanged data is wrong. |
User manual |
| Enabling and clearing CRC | The CRCEN bit of SPI_CTRL2 can be written only while SPIEN=0 and is valid only in full-duplex mode;in slave mode enable CRC after the clock is stable; in master mode CRC keeps running while NSS is high, so CRCSTOP can stop it. To clear CRC, disable SPIEN, clear CRCEN, set CRCEN again and enable SPIEN. |
User manual |
| Clearing the mode-error and overrun flags | MODERR is cleared by reading or writing SPI_STS and then writing SPI_CTRL1, and while MODERR is set the hardware does not allow SPIEN and MSEL to be set;OVER is cleared by reading SPI_DAT and then reading SPI_STS. |
User manual |
| Modes in which the sampling-delay register applies | The DELAYTIME field of SPI_CTRL3 can be configured only in SPI master full-duplex mode and SPI master receive mode, and configuring it in any other mode has no effect. |
User manual |
The errata list of the N32G412 and N32G415 errata sheet V1.0.0 (2026.6.30) does not list any SPI-related entries.