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 参考
以下 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 不同,其余配置一致。
/* 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。
/* 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 到达。
/* 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。
/* 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。
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 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 并按从机、主机的顺序重新配置。
/* 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. 第二阶段收发并比较。 此时 SPI2 为主设备,循环中先等 SPI2 发送缓冲空,再写主机与从机数据并读回双方接收数据。
/* 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,从机设为单线接收,分别打开发送空与接收非空中断。
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. 配置两个中断的优先级。 从机接收中断的抢占优先级高于主机发送中断,保证接收数据先被取走。
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 后关闭发送中断;从机中断判断接收非空并读走数据。
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. 主循环等待收满并比较结果。 主循环只等 RxIdx 达到 BufferSize,随后比较从机接收缓冲与主机发送缓冲。
/* 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,循环中先等发送缓冲空再写入,随后等接收非空读回。
/* 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 前先写一个字节到发送寄存器,进入循环后每收到一个字节就读走,若还有数据则补发下一个。
/* 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。
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。
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);
}
对应源码: 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 的重映射。
/* 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 请求。
/* 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 通道并等待完成。 两路通道都使能后,等待两个通道的传输完成标志,再比较从机接收缓冲与主机发送缓冲。
/* 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 控制。
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 清零,最后等接收非空并读回,保证读到的是本次交换的数据。
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。
/* 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。
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 搬运整页,等传输完成后关闭通道并把片选拉高。
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 搬运指定长度。
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. 主程序按 DMA 方式完成擦写读与校验。 主流程读 ID、擦除扇区,再用 DMA 写入、DMA 读回并比较,最后再次擦除读回确认全为 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. 引脚与 DMA 通道由宏定义。 头文件用宏把 SPI 实例、DMA 通道、重映射与各个引脚集中定义,移植时只改这一处。
/* 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 的重映射。
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 通道。
/* 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 完成。 主循环每字节等发送缓冲空后写入,发完全部数据后等从机接收通道传输完成,再比较结果。
/* 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 相关条目。