5.19 SPI 使用说明

N32G41x 的 SPI 模块以半双工、全双工或单线双向方式同步串行通信,支持主模式、从模式与多主模式、可编程数据位顺序、硬件或软件 NSS 管理、时钟极性与相位配置以及硬件 CRC 校验,驱动由头文件 n32g41x_spi.h 与实现文件 n32g41x_spi.c 构成,共 25 个公开函数。本 SDK 在 projects/n32g41x_EVAL/examples/SPI/ 下提供 8 个示例,覆盖全双工与单线收发、9 位数据帧、中断与 DMA 传输以及外接 SPI FLASH 的读写。寄存器位定义、工作模式以及 CRC 与错误标志的处理请配合用户手册的“串行外设接口/内置音频总线(SPI/I2S)”章节阅读,芯片已知问题以勘误手册为准。

5.19.1 相关文件

相对路径 作用
firmware/n32g41x_std_periph_driver/inc/n32g41x_spi.h 定义 SPI_InitType 结构体与数据方向、主从模式、帧长、时钟极性与相位、NSS、波特率预分频、位序、CRC 多项式等宏,并按复位与初始化、数据传输、NSS 管理、CRC、采样延迟、DMA、中断与标志管理七个区段声明 25 个公开函数
firmware/n32g41x_std_periph_driver/src/n32g41x_spi.c 实现上述 25 个函数:外设复位与初始化、数据收发、帧长与双向模式方向配置、NSS 与极性配置、CRC 计算与读取、采样延迟写入、DMA 与中断使能,以及状态标志与中断状态的读取和清除
projects/n32g41x_EVAL/examples/SPI/ SPI 示例根目录,含 CRC、FullDuplex_SoftNSS、Simplex_Interrupt、SPI_9Bit、SPI_DMA_T&R、SPI_FLASH、SPI_FLASH_DMA、SPI_Simplex_DMA_Recv 8 个示例
projects/n32g41x_EVAL/examples/SPI/SPI_FLASH/ 该示例的工程目录组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt
projects/n32g41x_EVAL/bsp/ 示例引用的板级支持文件:评估板 LED 与按键定义、毫秒与微秒延时函数

5.19.2 API 参考

搜索 API共 25 个函数

以下 25 个公开函数按 n32g41x_spi.h 的声明顺序分为 7 组;每个条目给出功能摘要与 n32g41x_spi.c 中该函数正上方的注释原文。

复位与初始化

SPI_I2S_DeInit、SPI_Init、SPI_InitStruct、SPI_Enable

数据传输

SPI_I2S_TransmitData、SPI_I2S_ReceiveData、SPI_ConfigDataLen、SPI_ConfigBidirectionalMode

NSS 管理

SPI_SetNssLevel、SPI_SSOutputEnable、SPI_NssPolSet、SPI_NSSFailCRCStopEnable

CRC

SPI_TransmitCrcNext、SPI_TransmitCrcNextCmd、SPI_EnableCalculateCrc、SPI_GetCRCDat、SPI_GetCRCPoly

采样延迟

SPI_DelayTime_Set、SPI_DelayTime_Get

DMA

SPI_I2S_EnableDma

中断与标志管理

SPI_I2S_EnableInt、SPI_I2S_GetStatus、SPI_ClrCRCErrFlag、SPI_I2S_GetIntStatus、SPI_I2S_ClrITPendingBit

void SPI_I2S_DeInit(const SPI_Module *SPIx)复位 SPI/I2S 寄存器。
源码注释
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)按 SPI_InitStruct 中的指定参数初始化 SPIx 外设。
源码注释
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

使用提示: 实现先清除 SPI_CTRL1 中数据方向、NSS、帧长、位序、时钟极性与相位、波特率预分频各位以及 SPI_CTRL2 的 DATFF9 位;仅当 SPIx 为 SPI2 且 DataLen 为 SPI_DATA_SIZE_9BITS 时写 SPI_CTRL2 的 DATFF9,其余情况把 DataLen 写入 SPI_CTRL1 的 DATFF。函数末尾还会清除 SPI_I2S_CFGR 的 I2S 使能位与模式位,把外设固定在 SPI 模式。

void SPI_InitStruct(SPI_InitType *SPI_StructInit)用默认值填充 SPI_InitStruct 的每个成员。
源码注释
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)使能或禁用指定的 SPI 外设。
源码注释
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)通过 SPIx/I2Sx 外设发送一个数据。
源码注释
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)从 DAT 寄存器读取 SPI/I2S 数据。
源码注释
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)配置所选 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

使用提示: 实现只改写 SPI_CTRL2 的 DATFF9 与 SPI_CTRL1 的 DATFF 之一:仅当 SPIx 为 SPI2 且 DataLen 为 SPI_DATA_SIZE_9BITS 时写 SPI_CTRL2 的 DATFF9,其余情况把 DataLen 写入 SPI_CTRL1 的 DATFF。

void SPI_ConfigBidirectionalMode(SPI_Module *SPIx, uint32_t DataDirection)为指定的 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)通过软件配置所选 SPI 的内部 NSS 引脚电平。
源码注释
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)使能或禁用所选 SPI 的 SS 输出。
源码注释
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 极性控制。
源码注释
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 失效后停止 CRC 计算。
源码注释
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)发送 SPIx 的 CRC 值。
源码注释
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)使能或禁用 SPIx CRC 值的发送。
源码注释
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)使能或禁用传输字节的 CRC 值计算。
源码注释
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)从 SPI_CRCTDAT/SPI_CRCRDAT 寄存器读取 SPI CRC 数据。
源码注释
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)从 SPI_CRCPOLY 寄存器读取 CRC 多项式。
源码注释
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 主机采样延迟时间配置。
源码注释
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)获取 SPI 主机采样时钟延迟时间。
源码注释
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)使能或禁用 SPIx/I2Sx 的 DMA 接口。
源码注释
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)使能或禁用指定的 SPI/I2S 中断。
源码注释
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)检查指定的 SPI/I2S 标志位是否置位。
源码注释
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)清除 SPI 标志位状态。
源码注释
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)检查指定的 SPI/I2S 中断是否发生。
源码注释
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).

使用提示: 实现同时检查 SPI_STS 中对应的事件标志与 SPI_CTRL2 中对应的中断使能位,只有两者都置位时才返回 SET。

void SPI_I2S_ClrITPendingBit(SPI_Module *SPIx, uint8_t SPI_I2S_IT)清除 SPIx 的 CRC 错误(CRCERR)中断挂起位。
源码注释
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.

使用提示: 实现取 SPI_I2S_IT 的低 4 位作为位序号,向 SPI_STS 写入该位取反后的值来清除挂起位。

5.19.3 Demo 示例说明

本节说明 SPI 的 8 个示例:CRC 用主从互接验证硬件 CRC,FullDuplex_SoftNSS 在同一对外设上交换主从角色,Simplex_Interrupt 演示单线中断收发,SPI_9Bit 演示 9 位数据帧,SPI_DMA_T&R 演示单线 DMA 收发,SPI_FLASH 与 SPI_FLASH_DMA 分别以轮询和 DMA 方式读写外接串行 FLASH,SPI_Simplex_DMA_Recv 演示单线 DMA 接收。

5.19.3.1 CRC 硬件 CRC 校验

工程目录: projects/n32g41x_EVAL/examples/SPI/CRC/

初始化时钟、SPI 引脚复用与打印串口→配置 SPI1 为主机、SPI2 为从机并开启 CRC 计算→循环收发 16 位数据→收发结束后各自发送本机 CRC→比较数据与 CRC 错误标志→输出 CRC1、CRC2 结果

项目 源码解读
功能 SPI1 作主机、SPI2 作从机,以 16 位全双工模式交换 32 个半字;
双方开启硬件 CRC 计算,在最后一个数据后各自发送本机 CRC 值,接收端比较数据与 CRC 并检查 CRC 错误标志。
源码组成 src/main.c:时钟与引脚配置、主从初始化、CRC 计算使能、收发循环与结果比较;
inc/main.h:SPI_MASTER/SPI_SLAVE 以及引脚与缓冲区长度等宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 主从均为 SPI_DIR_DOUBLELINE_FULLDUPLEX、SPI_DATA_SIZE_16BITS、SPI_NSS_SOFT、SPI_CLKPOL_HIGH、SPI_CLKPHA_FIRST_EDGE、SPI_FB_MSB、SPI_BR_PRESCALER_128,CRCPoly 为 7,缓冲区长度 32;
SPI1 为 SPI_MODE_MASTER,SPI2 为 SPI_MODE_SLAVE。
系统时钟为 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。
程序与数据路径 main 先使能双方 CRC 计算与 SPI,再循环 31 次:等主机发送缓冲空后依次写入从机与主机数据并读出双方接收数据;
循环后补发最后一个数据并各自调用 SPI_TransmitCrcNext 发送 CRC,再用 Buffercmp 比较数据、检查 SPI_CRCERR_FLAG 并读取双方 CRC。
主要 API SPI_InitStruct、SPI_Init、SPI_Enable、SPI_EnableCalculateCrc、SPI_I2S_TransmitData、SPI_I2S_ReceiveData、SPI_TransmitCrcNext、SPI_I2S_GetStatus
验证方法 串口打印 This is SPI CRC demo!!,通过时打印 Test PASS!! 及 CRC1、CRC2 的十六进制值,失败时打印 Test fail!! 及 Status1、Status2。
接线为 SPI1 的 PA5、PA6、PA7 分别接 SPI2 的 PB13、PB14、PB15,并共地。
使用提示 CRC 只在全双工模式有效,8 位帧按 CRC8、16 位帧按 CRC16 计算;
SPI_EnableCalculateCrc 置位时会清空 CRC 寄存器。
接收端读 CRC 值前须等对应 CRC 数据到达,在 BUSY 为 1 时读 CRC 寄存器可能得到错误值。
核心代码解读与移植

示例把两个 SPI 互接,用同一组数据双向收发并各自附加 CRC,接收端既比较数据也比较 CRC 错误标志。

数据路径: SPI_MASTER 与 SPI_SLAVE 的 SPI_InitType 配置 → SPI_Init → SPI_EnableCalculateCrc → SPI_I2S_TransmitData/SPI_I2S_ReceiveData 循环收发 → SPI_TransmitCrcNext 发送 CRC → Buffercmp 与 SPI_CRCERR_FLAG 判定结果

1. 主从按 16 位全双工初始化。 主机与从机共用同一结构体,只有 SpiMode 不同,其余配置一致。

src/main.csrc/main.c · main
    /* 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. 循环收发并保留最后一个数据用于发送 CRC。 循环条件为 TxIdx < BufferSize - 1,最后一个数据在循环外单独处理,以便紧跟其后发送 CRC。

src/main.csrc/main.c · main
    /* 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. 收发结束后发送双方 CRC。 先等发送缓冲空,再补发最后一个数据并立即请求发送 CRC,随后等待对端 CRC 到达。

src/main.csrc/main.c · main
    /* 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. 比较数据、检查 CRC 错误标志并读取 CRC 值。 Buffercmp 比较收发缓冲,任一侧 SPI_CRCERR_FLAG 置位即判失败,最后用 SPI_I2S_ReceiveData 读出对端 CRC。

src/main.csrc/main.c · main
    /* 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);

对应源码: src/main.c、inc/main.h

5.19.3.2 FullDuplex_SoftNSS 软件 NSS 全双工

工程目录: projects/n32g41x_EVAL/examples/SPI/FullDuplex_SoftNSS/

配置时钟、引脚复用与打印串口→第一阶段 SPI1 作主机、SPI2 作从机收发 8 位数据→比较收发结果→第二阶段交换主从角色并重新初始化→再次收发与比较→输出两阶段结果

项目 源码解读
功能 在同一对外设上先后跑两个阶段:第一阶段 SPI1 作主机、SPI2 作从机,第二阶段 SPI1 作从机、SPI2 作主机,均以 8 位全双工、软件 NSS 交换 32 字节数据并比较结果。
源码组成 src/main.c:时钟与引脚配置、两阶段初始化、收发循环与结果比较;
inc/main.h:SPI_MASTER/SPI_SLAVE 以及引脚与缓冲区长度等宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 两阶段均为 SPI_DIR_DOUBLELINE_FULLDUPLEX、SPI_DATA_SIZE_8BITS、SPI_NSS_SOFT、SPI_CLKPOL_LOW、SPI_CLKPHA_FIRST_EDGE、SPI_FB_LSB、SPI_BR_PRESCALER_8,CRCPoly 为 7,缓冲区长度 32。
第二阶段开始前对两路 SPI 先 SPI_I2S_DeInit 再重新初始化,并用 GPIO_Configuration 交换两侧引脚的主从角色。
程序与数据路径 main 先按第一阶段配置完成一次收发并用 Buffercmp 比较;
随后交换主从角色、复位并重新配置外设,清零收发缓冲与下标后按第二阶段再收发一次,最后打印两阶段结果。
主要 API SPI_InitStruct、SPI_Init、SPI_I2S_DeInit、SPI_Enable、SPI_I2S_TransmitData、SPI_I2S_ReceiveData、SPI_I2S_GetStatus
验证方法 串口依次打印 SPI FullDuplex SoftNSS Demo、Phase1: Master->Slave PASSED, Slave->Master PASSED 与 Phase2: Master->Slave PASSED, Slave->Master PASSED。
接线为 SPI1 的 PA5、PA6、PA7 分别接 SPI2 的 PB13、PB14、PB15,并共地。
使用提示 软件 NSS 下主从都可省略 NSS 引脚,但仍需正确交换 SCK 的驱动方向;
本例在第二阶段用 GPIO_Configuration 把 SPI1 引脚改为输入、SPI2 引脚改为复用输出,切换角色时必须调用。
SPI_I2S_DeInit 后再 SPI_Init 才能保证旧配置被清除。
核心代码解读与移植

示例在同一对外设上先后承担两种主从角色,用软件 NSS 完成两个方向的 8 位全双工收发。

数据路径: GPIO_Configuration 设定主从引脚 → SPI_InitStruct/SPI_Init 配置 SPI1、SPI2 → SPI_Enable → SPI_I2S_TransmitData/SPI_I2S_ReceiveData 收发 → Buffercmp 判定 → 交换角色后重复

1. 第一阶段按软件 NSS 全双工初始化。 主机与从机共用同一结构体,从机只改 SpiMode。

src/main.csrc/main.c · main
    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. 第一阶段收发并比较。 每个字节先写入从机发送寄存器再写入主机发送寄存器,随后依次读回双方接收数据,循环结束后比较两个方向的结果。

src/main.csrc/main.c · main
    /* 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. 第二阶段交换主从角色并重新初始化。 先由 GPIO_Configuration 交换两侧引脚方向,再复位两路 SPI 并按从机、主机的顺序重新配置。

src/main.csrc/main.c · main
    /* 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);
src/main.csrc/main.c · main
    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. 第二阶段收发并比较。 此时 SPI2 为主设备,循环中先等 SPI2 发送缓冲空,再写主机与从机数据并读回双方接收数据。

src/main.csrc/main.c · main
    /* 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);
    }

对应源码: src/main.c、inc/main.h

5.19.3.3 Simplex_Interrupt 单线中断收发

工程目录: projects/n32g41x_EVAL/examples/SPI/Simplex_Interrupt/

配置时钟、引脚复用、中断优先级与打印串口→配置 SPI1 为单线发送主机、SPI2 为单线接收从机→使能主机发送空中断与从机接收非空中断→中断服务函数逐字节收发→主循环等待完成后比较结果

项目 源码解读
功能 SPI1 作单线发送主机,通过发送空中断逐字节发送;
SPI2 作单线接收从机,通过接收非空中断逐字节接收,主循环等到收满后比较数据。
源码组成 src/main.c:时钟、GPIO 与 NVIC 配置、收发主循环与比较;
src/n32g41x_it.c:SPI1_IRQHandler 与 SPI2_IRQHandler;
inc/main.h:SPI_MASTER/SPI_SLAVE、中断号与引脚等宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 主机 SPI_DIR_SINGLELINE_TX、从机 SPI_DIR_SINGLELINE_RX,均 SPI_DATA_SIZE_8BITS、SPI_NSS_HARD、SPI_CLKPOL_HIGH、SPI_CLKPHA_FIRST_EDGE、SPI_FB_MSB、SPI_BR_PRESCALER_8;
主机调用 SPI_SSOutputEnable 输出 NSS。
NVIC 采用 NVIC_PriorityGroup_1,SPI1 中断抢占优先级 1、子优先级 2,SPI2 中断抢占优先级 0、子优先级 1。
程序与数据路径 main 配置单线主从并使能发送空、接收非空中断后进入等待循环;
主机中断每进一次发一个字节,发到 BufferSize 时关闭发送中断;
从机中断每进一次收一个字节;
收满后 Buffercmp 比较接收缓冲与发送缓冲。
主要 API SPI_InitStruct、SPI_Init、SPI_SSOutputEnable、SPI_I2S_EnableInt、SPI_Enable、SPI_I2S_GetIntStatus、SPI_I2S_TransmitData、SPI_I2S_ReceiveData
验证方法 串口打印 SPI Simplex Interrupt Demo,通过时打印 Test PASSED!,失败时打印 Test FAILED!。
接线为主机 MOSI(PA7)接从机 MISO(PB14),主机 NSS、SCK 分别接从机 NSS、SCK,并共地。
使用提示 单线发送时主机的发送引脚与单线接收时从机的接收引脚接在一起,两端的 NSS 与 SCK 也须相连;
主机需要 SPI_SSOutputEnable 才能拉低 NSS。
两个中断服务函数在发满、收满后要自行停止中断,否则会反复进入。
核心代码解读与移植

示例用单线模式只走一个数据方向,主机靠发送空中断、从机靠接收非空中断各推动一个字节。

数据路径: SPI_InitType 配置 → SPI_Init → SPI_I2S_EnableInt 使能发送空/接收非空中断 → 主机 SPI1_IRQHandler 与从机 SPI2_IRQHandler 逐字节收发 → 主循环 Buffercmp 比较

1. 配置单线主从与中断。 主机设为单线发送并输出 NSS,从机设为单线接收,分别打开发送空与接收非空中断。

src/main.csrc/main.c · main
    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. 配置两个中断的优先级。 从机接收中断的抢占优先级高于主机发送中断,保证接收数据先被取走。

src/main.csrc/main.c · NVIC_Configuration
    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. 在中断服务函数里逐字节收发。 主机中断用 SPI_I2S_GetIntStatus 判断发送空,发满 BufferSize 后关闭发送中断;从机中断判断接收非空并读走数据。

src/n32g41x_it.csrc/n32g41x_it.c · SPI1_IRQHandler
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);
        }
    }
}
src/n32g41x_it.csrc/n32g41x_it.c · SPI2_IRQHandler
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. 主循环等待收满并比较结果。 主循环只等 RxIdx 达到 BufferSize,随后比较从机接收缓冲与主机发送缓冲。

src/main.csrc/main.c · main
    /* 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");
    }

对应源码: src/main.c、src/n32g41x_it.c、inc/main.h

5.19.3.4 SPI_9Bit 9 位数据帧

工程目录: projects/n32g41x_EVAL/examples/SPI/SPI_9Bit/

配置时钟、引脚复用与打印串口→主机延时等待从机就绪→SPI2 主从均配置为 9 位全双工、软件 NSS→从机预装载首字节并按接收非空收发→主机收发并只比较低 9 位→通过时点亮 LED

项目 源码解读
功能 由 SPI_9Bit_Master 与 SPI_9Bit_Slave 两个子工程分别烧写到两块板,SPI2 以 9 位数据帧、软件 NSS 全双工交换 32 个数据;
主机发 0x101 起、期望 0x151 起,从机发 0x151 起、期望 0x101 起,只比较低 9 位。
源码组成 SPI_9Bit_Master/src/main.c 与 SPI_9Bit_Slave/src/main.c:各自的主从初始化、收发与比较;
两个子工程的 inc/main.h:SPI2 引脚与期望数据等宏;
readme.txt:功能、环境与接线说明;
各子工程的 MDK-ARM/、EWARM/。
关键配置 主从均为 SPI_DATA_SIZE_9BITS、SPI_DIR_DOUBLELINE_FULLDUPLEX、SPI_NSS_SOFT、SPI_CLKPOL_LOW、SPI_CLKPHA_FIRST_EDGE、SPI_FB_MSB、SPI_BR_PRESCALER_64,缓冲区长度 32;
9 位帧只有 SPI2 支持且不使用 CRC。
主机上电延时 1 s 等从机就绪。
程序与数据路径 主机进入发送循环:等发送缓冲空后写入 9 位数据,再等接收非空读回;
循环结束用只比较低 9 位的 Buffercmp 与期望缓冲比较。
从机使能 SPI 前先预装载首字节,之后每收到一个字节就读走并补发下一个,最后同样比较低 9 位。
主要 API SPI_InitStruct、SPI_Init、SPI_Enable、SPI_I2S_TransmitData、SPI_I2S_ReceiveData、SPI_I2S_GetStatus
验证方法 主机串口打印 SPI2 9-Bit Master Demo,从机打印 SPI2 9-Bit Slave Demo,通过时各自打印 9-Bit Transfer PASSED! 并点亮 LED1。
两板同名引脚对接(PB13 接 PB13、PB14 接 PB14、PB15 接 PB15)并共地。
使用提示 只有 SPI2 支持 9 位帧,SPI1 只支持 8 位与 16 位;
9 位帧不支持 CRC。
接收到的 9 位数据只占 DAT[8:0],高位为 0,所以比较时要用低 9 位掩码。
两块板要先烧从机再烧主机,主机上电延时 1 s 是为等从机就绪。
核心代码解读与移植

成对子工程在两块板上跑同一套 9 位协议,双方只比较接收数据的低 9 位。

数据路径: 主机 main 配置并延时 → SPI_I2S_TransmitData/SPI_I2S_ReceiveData 收发 → 低 9 位 Buffercmp;从机 main 配置 → 预装载首字节 → 接收非空读走并补发 → 低 9 位 Buffercmp

1. 主机按 9 位全双工配置并收发。 结构体设 SPI_DATA_SIZE_9BITS,循环中先等发送缓冲空再写入,随后等接收非空读回。

SPI_9Bit_Master/src/main.cSPI_9Bit_Master/src/main.c · main
    /* 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. 从机预装载首字节并按接收非空收发。 从机使能 SPI 前先写一个字节到发送寄存器,进入循环后每收到一个字节就读走,若还有数据则补发下一个。

SPI_9Bit_Slave/src/main.cSPI_9Bit_Slave/src/main.c · main
    /* 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. 主机延时等待从机就绪。 板对板测试时从机上电就绪较慢,主机在配置引脚前先延时 1 s。

SPI_9Bit_Master/src/main.cSPI_9Bit_Master/src/main.c · main
    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. 只比较低 9 位并在通过时点亮 LED。 Buffercmp 对每个元素用 0x01FF 掩码取低 9 位比较,避开 9 位帧高位为 0 的差异;通过后点亮 LED1。

SPI_9Bit_Master/src/main.cSPI_9Bit_Master/src/main.c · Buffercmp
TestStatus Buffercmp(uint16_t* pBuffer1, uint16_t* pBuffer2, uint16_t BufferLength)
{
    while (BufferLength--)
    {
        if ((*pBuffer1 & 0x01FF) != (*pBuffer2 & 0x01FF))
        {
            return FAILED;
        }
        pBuffer1++;
        pBuffer2++;
    }
    return PASSED;
}
SPI_9Bit_Master/src/main.cSPI_9Bit_Master/src/main.c · main
    if (TransferStatus == PASSED)
    {
        log_info("9-Bit Transfer PASSED!\r\n");
        /* LED1 ON: test passed */
        GPIO_ResetBits(LED1_PORT, LED1_PIN);
    }

对应源码: 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 单线 DMA 收发

工程目录: projects/n32g41x_EVAL/examples/SPI/SPI_DMA_T&R/

配置时钟、DMA 与 SPI 引脚复用→配置从机接收、主机发送两个 DMA 通道并重映射→配置 SPI1 为单线发送主机、SPI2 为单线接收从机→使能 SPI 的 DMA 请求与两个 DMA 通道→等待传输完成并比较结果

项目 源码解读
功能 SPI1 作单线发送主机、SPI2 作单线接收从机,主机发送缓冲经 DMA 通道搬入 SPI1 数据寄存器,从机接收由 DMA 通道搬出到内存,传输完成后比较数据。
源码组成 src/main.c:时钟、GPIO 与 DMA 配置、主从初始化、DMA 使能与比较;
inc/main.h:SPI_MASTER/SPI_SLAVE、DMA 通道与重映射、引脚与缓冲区长度等宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 主机 SPI_DIR_SINGLELINE_TX、从机 SPI_DIR_SINGLELINE_RX,均 SPI_DATA_SIZE_8BITS、SPI_NSS_HARD、SPI_CLKPOL_HIGH、SPI_CLKPHA_FIRST_EDGE、SPI_FB_MSB、SPI_BR_PRESCALER_8;
从机接收用一路 DMA 通道(外设作源、内存递增),主机发送用另一路 DMA 通道(外设作目标);
两者做 SPI1 TX、SPI2 RX 的 DMA 请求重映射。
程序与数据路径 main 先初始化两个 DMA 通道并做重映射,再配置单线主从并调用 SPI_SSOutputEnable 与 SPI_I2S_EnableDma,随后使能两个 DMA 通道,等待两个通道的传输完成标志后 Buffercmp 比较从机接收缓冲与主机发送缓冲。
主要 API DMA_Init、DMA_RequestRemap、SPI_InitStruct、SPI_Init、SPI_SSOutputEnable、SPI_I2S_EnableDma、SPI_Enable
验证方法 串口打印 This is SPI DMA demo!!,通过时打印 Test PASS!!,失败时打印 Test fail!!。
接线为主机 SCK(PA5)接从机 SCK(PB13)、主机 MOSI(PA7)接从机 MISO(PB14)、主机 NSS(PA4)接从机 NSS(PB12),并共地。
使用提示 单线发送时主机 MOSI 与从机 MISO 相连;
主机需要 SPI_SSOutputEnable 才能输出 NSS。
DMA 使能要在 SPI 使能前后按示例顺序调用,且必须在 SPI_I2S_EnableDma 与 DMA_EnableChannel 都就绪后数据才会开始搬运。
核心代码解读与移植

示例把单线 SPI 的收发各交给一路 DMA,CPU 只负责配置与等待完成。

数据路径: DMA_Init/DMA_RequestRemap 配置两路通道 → SPI_Init 配置单线主从 → SPI_I2S_EnableDma → DMA_EnableChannel → DMA 与 SPI 搬运数据 → DMA_GetFlagStatus 等待完成 → Buffercmp 比较

1. 配置从机接收与主机发送两路 DMA 通道。 从机接收方向外设作源、内存递增;主机发送方向外设作目标;两个通道分别做 SPI2 RX 与 SPI1 TX 的重映射。

src/main.csrc/main.c · main
    /* 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. 配置单线主从并打开 SPI 的 DMA 请求。 主机设为单线发送并输出 NSS,从机设为单线接收,随后使能从机接收与主机发送的 DMA 请求。

src/main.csrc/main.c · main
    /* 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. 使能 DMA 通道并等待完成。 两路通道都使能后,等待两个通道的传输完成标志,再比较从机接收缓冲与主机发送缓冲。

src/main.csrc/main.c · main
    /* 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);

对应源码: src/main.c、inc/main.h

5.19.3.6 SPI_FLASH 轮询读写外部 FLASH

工程目录: projects/n32g41x_EVAL/examples/SPI/SPI_FLASH/

初始化 SPI1 主机与片选 GPIO→读 FLASH 器件 ID→擦除扇区→轮询方式写入一段数据再读回→比较写入与读回的数据→再次擦除扇区并确认全部为 0xFF

项目 源码解读
功能 SPI1 作全双工主机,以轮询方式经 SPI 驱动外接 W25Q128 串行 FLASH:读器件 ID、擦除扇区、写入一段数据并读回比较、再次擦除并确认数据被擦成 0xFF。
源码组成 src/main.c:主流程与 Buffercmp;
src/spi_flash.c:sFLASH_Init、sFLASH_SendByte、sFLASH_ReadID、擦除与读写等驱动函数;
inc/spi_flash.h:SPI 引脚、命令码与函数声明;
inc/main.h:地址与缓冲区等宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 sFLASH_Init 把 SPI1 配为 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、SPI_FB_MSB;
片选 PA4 当普通 GPIO 由 sFLASH_CS_LOW/sFLASH_CS_HIGH 控制;
SCK、MISO、MOSI 为 PA5、PA6、PA7。
程序与数据路径 main 先 sFLASH_Init 再 sFLASH_ReadID,确认 ID 为 W25Q128 后擦除扇区、调用 sFLASH_WriteBuffer 写入文本缓冲并 sFLASH_ReadBuffer 读回,用 Buffercmp 比较;
随后再次擦除并逐字节确认读回值都是 0xFF。
主要 API SPI_InitStruct、SPI_Init、SPI_Enable、SPI_I2S_TransmitData、SPI_I2S_ReceiveData、SPI_I2S_GetStatus
验证方法 串口打印 This is SPI FLASH demo!! 及读到的 ID,随后打印 Write data to flash OK.、SPI write and read flash OK. 与 Sector erase OK.。
评估板本身不带 FLASH,需外接 W25Q128 并按 SCK、MISO、MOSI、CS 依次接 PA5、PA6、PA7、PA4。
使用提示 sFLASH_SendByte 发送后要依次等发送空、BUSY 清零、接收非空再读回,轮询顺序不能颠倒,否则会读到过期数据。
W25Q128 支持多字节 ID,本例同时接受 0x00EF4018 与 0x00EF7018 两种返回值。
写入前必须先擦除对应扇区。
核心代码解读与移植

示例把 SPI1 当主机轮询驱动外接串行 FLASH,完成擦除、写入、读回与校验的一整套流程。

数据路径: sFLASH_Init 配置 SPI1 → sFLASH_ReadID 确认器件 → sFLASH_EraseSector → sFLASH_WriteBuffer/sFLASH_ReadBuffer 经 sFLASH_SendByte 收发 → Buffercmp 比较

1. 初始化 SPI1 主机。 驱动把 SPI1 配为全双工主机、模式 3(CLKPOL_HIGH 加 CLKPHA_SECOND_EDGE),片选另用 GPIO 控制。

src/spi_flash.csrc/spi_flash.c · sFLASH_Init
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. 轮询收发一个字节。 每次发送先等发送缓冲空,写完再等发送空与 BUSY 清零,最后等接收非空并读回,保证读到的是本次交换的数据。

src/spi_flash.csrc/spi_flash.c · sFLASH_SendByte
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. 读器件 ID 并走完整的擦写读流程。 主流程先读 ID 判断器件,再擦除扇区、写入并读回数据、比较结果,最后再次擦除并确认读出 0xFF。

src/main.csrc/main.c · main
    /* 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. 读 ID 命令的收发序列。 sFLASH_ReadID 拉低片选后发送 0x9F,再连收三个字节拼接成 24 位 ID。

src/spi_flash.csrc/spi_flash.c · sFLASH_ReadID
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;
}

对应源码: src/main.c、src/spi_flash.c、inc/spi_flash.h、inc/main.h

5.19.3.7 SPI_FLASH_DMA DMA 读写外部 FLASH

工程目录: projects/n32g41x_EVAL/examples/SPI/SPI_FLASH_DMA/

初始化 SPI1 主机与片选 GPIO→读 FLASH 器件 ID→擦除扇区→写入时切换为单线发送并用 DMA 搬运→读取时切换为只读双线并用 DMA 搬运→比较写读数据并确认擦除结果

项目 源码解读
功能 SPI1 作主机,以 DMA 方式驱动外接 W25Q128 串行 FLASH:写页时把 SPI 切到单线发送、由发送 DMA 搬运,读时切到只读双线、由接收 DMA 搬运,完成后比较写读数据并确认擦除后全为 0xFF。
源码组成 src/main.c:主流程与 Buffercmp;
src/spi_flash.c:sFLASH_Init、sFLASH_DMA_PageWrite、sFLASH_DMA_ReadBuffer、sFLASH_DMA_WriteBuffer 等驱动函数;
inc/spi_flash.h:SPI 引脚、DMA 通道、命令码与函数声明;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 sFLASH_Init 把 SPI1 配为全双工主机、SPI_DATA_SIZE_8BITS、SPI_CLKPOL_HIGH、SPI_CLKPHA_SECOND_EDGE、SPI_NSS_SOFT、SPI_BR_PRESCALER_8;
写页时切到 SPI_DIR_SINGLELINE_TX、SPI_BR_PRESCALER_4,读时切到 SPI_DIR_DOUBLELINE_RONLY、SPI_BR_PRESCALER_4,用完后恢复全双工。
接收用 DMA_CH2、发送用 DMA_CH3。
SCK、MISO、MOSI、CS 为 PA1、PA0、PA2、PA4。
程序与数据路径 main 先 sFLASH_Init 与 sFLASH_ReadID,确认 ID 后擦除扇区,调用 sFLASH_DMA_WriteBuffer 分页 DMA 写入、sFLASH_DMA_ReadBuffer DMA 读回并用 Buffercmp 比较;
随后再次擦除并由 DMA 读回确认全为 0xFF。
主要 API SPI_InitStruct、SPI_Init、SPI_Enable、SPI_I2S_EnableDma、DMA_Init、DMA_EnableChannel、SPI_I2S_TransmitData、SPI_I2S_ReceiveData
验证方法 串口打印读到的 ID,随后打印 spi dma write and read flash OK. 与 Sector erase OK.。
评估板本身不带 FLASH,需外接 W25Q128 并按 SCK、MISO、MOSI、CS 依次接 PA1、PA0、PA2、PA4,且要取下与 LED1 复用 PA1 的跳线。
使用提示 本例系统时钟为 HSE+PLL 72 MHz,SPI 最高 36 MHz,与其它示例的时钟配置不同。
DMA 写入前把 SPI 切到单线发送、读取前切到只读双线,用完后都要恢复到全双工全配置,否则后续字节收发模式不对。
切模式时先关闭并使能 SPI 才能写入方向字段。
核心代码解读与移植

示例在轮询读写 FLASH 的基础上把数据搬运交给 DMA,并在写、读阶段临时切换 SPI 的工作模式。

数据路径: sFLASH_Init 配置 SPI1 → sFLASH_DMA_PageWrite 切单线发送 → DMA_EnableChannel 搬运 → 恢复全双工 → sFLASH_DMA_ReadBuffer 切只读双线 → DMA 搬运 → Buffercmp 比较

1. 写页阶段切到单线发送并启动 DMA。 函数先发写命令,把 SPI 切到单线发送后用发送 DMA 搬运整页,等传输完成后关闭通道并把片选拉高。

src/spi_flash.csrc/spi_flash.c · sFLASH_DMA_PageWrite
    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. 读命令后切到只读双线并启动接收 DMA。 先拉低片选并发出读命令与 24 位地址,再把 SPI 切到只读双线,用接收 DMA 搬运指定长度。

src/spi_flash.csrc/spi_flash.c · sFLASH_DMA_ReadBuffer
    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);
src/spi_flash.csrc/spi_flash.c · sFLASH_DMA_ReadBuffer
    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. 主程序按 DMA 方式完成擦写读与校验。 主流程读 ID、擦除扇区,再用 DMA 写入、DMA 读回并比较,最后再次擦除读回确认全为 0xFF。

src/main.csrc/main.c · main
    /* 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. 引脚与 DMA 通道由宏定义。 头文件用宏把 SPI 实例、DMA 通道、重映射与各个引脚集中定义,移植时只改这一处。

inc/spi_flash.hinc/spi_flash.h
/* 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

对应源码: src/main.c、src/spi_flash.c、inc/spi_flash.h

5.19.3.8 SPI_Simplex_DMA_Recv 单线 DMA 接收

工程目录: projects/n32g41x_EVAL/examples/SPI/SPI_Simplex_DMA_Recv/

配置时钟、DMA 与 SPI 引脚复用→配置从机接收 DMA 通道并重映射→配置 SPI1 为单线发送主机、SPI2 为单线接收从机→主机轮询逐字节发送→从机经 DMA 接收→等待传输完成并比较结果

项目 源码解读
功能 SPI1 作单线发送主机,用轮询方式逐字节发送;
SPI2 作单线接收从机,接收数据由 DMA 通道搬入内存,传输完成后比较数据。
源码组成 src/main.c:时钟、GPIO 与 DMA 配置、主从初始化、轮询发送与比较;
inc/main.h:SPI_MASTER/SPI_SLAVE、DMA 通道与重映射、引脚与缓冲区长度等宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 主机 SPI_DIR_SINGLELINE_TX、从机 SPI_DIR_SINGLELINE_RX,均 SPI_DATA_SIZE_8BITS、SPI_NSS_HARD、SPI_CLKPOL_HIGH、SPI_CLKPHA_FIRST_EDGE、SPI_FB_MSB、SPI_BR_PRESCALER_8;
从机接收用一路 DMA 通道(外设作源、内存递增)并做 SPI2 RX 重映射。
程序与数据路径 main 先初始化从机接收 DMA 通道并重映射,再配置单线主从、调用 SPI_SSOutputEnable 与从机 SPI_I2S_EnableDma,使能从机 DMA 通道后由主循环轮询发送整段数据,等到通道传输完成标志后 Buffercmp 比较从机接收缓冲与主机发送缓冲。
主要 API DMA_Init、DMA_RequestRemap、SPI_InitStruct、SPI_Init、SPI_SSOutputEnable、SPI_I2S_EnableDma、SPI_Enable、SPI_I2S_TransmitData
验证方法 串口打印 This is SPI Simplex DMA Recv demo!!,通过时打印 Test PASS!!,失败时打印 Test fail!!。
接线为主机 SCK(PA5)接从机 SCK(PB13)、主机 MOSI(PA7)接从机 MISO(PB14)、主机 NSS(PA4)接从机 NSS(PB12),并共地。
使用提示 本例只有从机接收用 DMA,主机发送仍是轮询,因此主机发送速度要低于从机 DMA 搬运能力。
单线发送时主机 MOSI 与从机 MISO 相连;
主机需要 SPI_SSOutputEnable 才能输出 NSS。
核心代码解读与移植

示例把单线接收的搬运交给 DMA,发送侧仍由主机轮询逐字节推动。

数据路径: DMA_Init/DMA_RequestRemap 配置从机接收通道 → SPI_Init 配置单线主从 → SPI_I2S_EnableDma/DMA_EnableChannel → 主机轮询 SPI_I2S_TransmitData → DMA 搬入内存 → Buffercmp 比较

1. 配置从机接收 DMA 通道。 从机接收方向外设作源、内存递增,并做 SPI2 RX 的重映射。

src/main.csrc/main.c · main
    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. 配置单线主从并使能从机 DMA 接收。 主机设为单线发送并输出 NSS,从机设为单线接收并打开接收 DMA 请求,随后使能从机 DMA 通道。

src/main.csrc/main.c · main
    /* 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. 主机轮询发送并等待 DMA 完成。 主循环每字节等发送缓冲空后写入,发完全部数据后等从机接收通道传输完成,再比较结果。

src/main.csrc/main.c · main
    /* 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);

对应源码: src/main.c、inc/main.h

5.19.4 注意与排障

场景 检查与建议 来源
9 位数据帧的适用范围 只有 SPI2 支持 9 位数据帧,SPI1 只支持 8 位与 16 位;
帧长须在使能 SPI 之前确定,因为 SPI_CTRL1 的 DATFF 只能在 SPIEN=0 时写入。
用户手册
9 位帧与 CRC 不能同时使用 SPI_CTRL2 的 DATFF9=1 时数据帧固定为 9 位,无论 SPI_CTRL1 的 DATFF 如何配置,且 9 位帧不支持 CRC。 用户手册
通信过程中不能改动的位 通信进行时不要修改 SPI_CTRL1 的 LSBFF、MSEL、CLKPHA、CLKPOL 与 BR,需要改动时应先关闭 SPI。 用户手册
主从时钟模式必须一致 不论选择哪种时序模式,主设备与从设备的 CLKPOL、CLKPHA 配置必须相同,否则收发数据出错。 用户手册
CRC 的使能与清除 SPI_CTRL2 的 CRCEN 只能在 SPIEN=0 时写入且只在全双工模式有效;
从机应在时钟稳定后再使能 CRC;
主机在 NSS 为高时仍会继续计算,可设置 CRCSTOP 停止。
清除 CRC 的步骤为关闭 SPIEN、清除 CRCEN、再次置位 CRCEN、使能 SPIEN。
用户手册
模式错误与溢出标志的清除 MODERR 需先读或写 SPI_STS 再写 SPI_CTRL1 来清除,且 MODERR 置位时硬件不允许设置 SPIEN 与 MSEL;
OVER 需先读 SPI_DAT 再读 SPI_STS 来清除。
用户手册
采样延迟寄存器的适用模式 SPI_CTRL3 的 DELAYTIME 只能在 SPI 主机全双工模式与主机接收模式下配置,其余模式下配置无效。 用户手册

N32G412、N32G415 系列勘误手册 V1.0.0(2026.6.30)的勘误列表未列出 SPI 相关条目。

国民技术开发套件文档中心

按芯片系列在线查阅国民技术 MCU 开发套件的芯片资料、SDK、驱动 API 与 Demo 示例说明。

© 2026 国民技术股份有限公司 版权所有粤ICP备11069485号本站使用不含 Cookie 的匿名访问统计