Xiashiqi 发表于 2025-8-26 10:47

CW32F030C8T6 SPI主从通信详解

一.CW32F030C8T6 SPI 功能
串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信。 CW32x030 内部集成 2 个串行外设 SPI 接口,支持双向全双工、单线半双工和单工通信模式,可配置 MCU 作为 主机或从机,支持多主机通信模式,支持直接内存访问(DMA)。
1.1主要特性

支持主机模式、从机模式
支持全双工、单线半双工、单工
可选的 4 位到 16 位数据帧宽度
支持收发数据 LSB 或 MSB 在前
可编程时钟极性和时钟相位
主机模式下通信速率高达 PCLK/4
从机模式下通信速率高达 PCLK/4
支持多机通信模式
8 个带标志位的中断源
支持直接内存访问 (DMA)
1.2功能框图




1.3SPI 引脚选择




1.4端口配置




1.5通信时序




1.6数据帧格式

数据帧宽度由控制寄存器 SPIx_CR1 的 WIDTH 位域配置,可设置 4 ~ 16bit 任意数据位宽。 数据的大小端由控制寄存器 SPIx_CR1的 LSBF位域配置,可选择最高有效位在前(MSB)或最低有效位在前(LSB)。
1.7时钟频率

同步串行时钟 SCK 信号由 SPI 主机控制产生,其时钟来源是 PCLK,通过配置控制寄存器 SPIx_CR1 的 BR 位域 来设置分频因子,可选择 2 ~ 128 分频。对于 SPI 从机,配置 SPIx_CR1.BR 无影响。
1.8时钟极性、时钟相位

时钟极性 CPOL,指设备处于没有数据传输的空闲状态时,SCK 串行时钟线的电平状态。通过控制寄存器 SPIx_CR1 的 CPOL 位域进行配置:设置 SPIx_CR1.CPOL 为 0,SCK 时钟线在空闲时为低电平;设置 SPIx_CR1.CPOL 为 1, SCK 时钟线在空闲时为高电平。
时钟相位 CPHA,指数据的采样和移位时刻。通过控制寄存器 SPIx_CR1 的 CPHA 进行配置:设置 SPIx_CR1. CPHA 为 0,在 SCK 的前边沿(SCK 由空闲状态变为非空闲状态的时钟边沿)采样、后边沿(SCK 由非空闲状态 变为空闲状态的时钟边沿)移位;设置 SPIx_CR1.CPHA 为 1,在 SCK 的前边沿移位、后边沿采样。
根据时钟极性 CPOL 和时钟相位 CPHA 的不同配置,SPI 可设置 4 种电平模式,主机和从机需要配置成相同的电 平模式才能保证正常通信。




1.9 CS引脚




1.10工作模式-全双工模式










1.10工作模式-单线半双工模式










1.11工作模式-单工模式










1.11多机通讯







1.12状态标志







1.13SPI 中断




1.14 SPI DMA




1.15 编程示例
















二.SPI主从通信示例(带DMA)
以下是为代码添加的详细注释,以增强可读性和理解性:

宏定义部分
#defineSPI_MASTER //主机模式
//#defineSPI_SLAVE//从机模式(当前注释掉,表示未启用)

// SPI模块配置
#defineSPIx                           CW_SPI2//使用SPI2模块
#defineSPIx_CLK                     RCC_APB1_PERIPH_SPI2//SPI2时钟源
#defineSPIx_APBClkENx               RCC_APBPeriphClk_Enable1//APB时钟使能函数

// SPI引脚配置(SCK、MISO、MOSI、CS)
#defineSPIx_SCK_GPIO_CLK            RCC_AHB_PERIPH_GPIOA//SCK引脚时钟
#defineSPIx_SCK_GPIO_PORT             CW_GPIOA//SCK引脚端口
#defineSPIx_SCK_GPIO_PIN            GPIO_PIN_2//SCK引脚号
#defineSPIx_SCK_AF()                  PA02_AFx_SPI2SCK()//SCK复用功能配置

#defineSPIx_MISO_GPIO_CLK             RCC_AHB_PERIPH_GPIOA//MISO引脚时钟
#defineSPIx_MISO_GPIO_PORT            CW_GPIOA//MISO引脚端口
#defineSPIx_MISO_GPIO_PIN             GPIO_PIN_0//MISO引脚号
#defineSPIx_MISO_AF()               PA00_AFx_SPI2MISO()//MISO复用功能配置

#defineSPIx_MOSI_GPIO_CLK             RCC_AHB_PERIPH_GPIOA//MOSI引脚时钟
#defineSPIx_MOSI_GPIO_PORT            CW_GPIOA//MOSI引脚端口
#defineSPIx_MOSI_GPIO_PIN             GPIO_PIN_1//MOSI引脚号
#defineSPIx_MOSI_AF()               PA01_AFx_SPI2MOSI()//MOSI复用功能配置

#defineSPIx_CS_GPIO_CLK               RCC_AHB_PERIPH_GPIOA//CS引脚时钟
#defineSPIx_CS_GPIO_PORT            CW_GPIOA//CS引脚端口
#defineSPIx_CS_GPIO_PIN               GPIO_PIN_3//CS引脚号
#defineSPIx_CS_AF()                   PA03_AFx_SPI2CS()//CS复用功能配置

// CS引脚电平控制
#defineSPIx_CS_LOW()                  PA03_SETLOW()//拉低CS(选中从机)
#defineSPIx_CS_HIGH()               PA03_SETHIGH() //拉高CS(取消选中)

// DMA配置
#defineSPIx_RX_DMACHANNEL             CW_DMACHANNEL1//RX DMA通道
#defineSPIx_TX_DMACHANNEL             CW_DMACHANNEL2//TX DMA通道
#defineSPIx_DMA_RxTrigSource          DMA_HardTrig_SPI2_RXBufferNE//RX触发源
#defineSPIx_DMA_TxTrigSource          DMA_HardTrig_SPI2_TXBufferE//TX触发源
#defineBufferSize                     ARRAY_SZ(TxBuffer)//缓冲区大小



枚举与全局变量
typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus; //测试状态枚举

// 发送和接收缓冲区
uint8_t TxBuffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                      0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
                      0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
                      0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
                      0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x55}; //发送数据(末尾0x55为结束标志)
uint8_t RxBuffer; //接收缓冲区
volatile TestStatus TransferStatus = FAILED; //传输状态标志



主函数
int32_t main(void)
{
    RCC_Configuration();   //初始化系统时钟和外设时钟
    GPIO_Configuration();//配置GPIO引脚和复用功能
    DMA_Configuration();   //配置DMA通道和触发源
    SPI_Configuration();   //配置SPI模块参数(模式、速率等)

#ifdef SPI_MASTER
    SPIx_CS_LOW(); //主机模式下拉低CS,选中从机
#endif

#ifdef SPI_SLAVE
    SPI_FlushSendBuff(SPIx); //从机模式下清空发送缓冲区
#endif

    SPI_DMACmd(SPIx, SPI_DMAReq_Tx | SPI_DMAReq_Rx, ENABLE); //启用SPI DMA传输

    while (1)
    {
      if (RxBuffer == 0x55) //检测接收完成标志
      {
#ifdef SPI_MASTER
            SPIx_CS_HIGH(); //主机模式下释放CS
#endif
            PB01_SETHIGH(); //点亮LED1指示传输完成

            TransferStatus = Buffercmp(RxBuffer, TxBuffer, BufferSize); //校验数据
            if (TransferStatus == PASSED)
            {
                PA07_SETHIGH(); //校验成功时点亮LED2
            }
      }
    }
}



时钟配置函数
void RCC_Configuration(void)
{
    // 使能GPIO、DMA、FLASH等外设时钟
    RCC_AHBPeriphClk_Enable(SPIx_SCK_GPIO_CLK | SPIx_MISO_GPIO_CLK | SPIx_MOSI_GPIO_CLK |
                           SPIx_CS_GPIO_CLK | RCC_AHB_PERIPH_DMA | RCC_AHB_PERIPH_GPIOA |
                           RCC_AHB_PERIPH_GPIOB | RCC_AHB_PERIPH_FLASH, ENABLE);
    SPIx_APBClkENx(SPIx_CLK, ENABLE); //使能SPI时钟

    // 配置系统时钟为64MHz(HSI->PLL)
    RCC_HSI_Enable(RCC_HSIOSC_DIV6);
    RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, RCC_PLL_MUL_8);
    FLASH_SetLatency(FLASH_Latency_3); //Flash等待周期设为3(频率>48MHz时需要)
    RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL); //切换系统时钟到PLL
}



GPIO 配置函数注释
void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure; // GPIO初始化结构体

    // 配置SPI引脚复用功能(SCK/MISO/MISO需复用为SPI功能)
    SPIx_SCK_AF();// SCK引脚复用为SPI功能
    SPIx_MISO_AF(); // MISO引脚复用为SPI功能
    SPIx_MOSI_AF(); // MOSI引脚复用为SPI功能

#ifdef SPI_MASTER
    // 主机模式配置:SCK、MOSI、CS为推挽输出,MISO为浮空输入
    GPIO_InitStructure.Pins = SPIx_SCK_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;    // 高速输出
    GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = SPIx_MOSI_GPIO_PIN;
    GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = SPIx_CS_GPIO_PIN;
    GPIO_Init(SPIx_CS_GPIO_PORT, &GPIO_InitStructure);

    // MISO引脚配置为浮空输入
    GPIO_InitStructure.Pins = SPIx_MISO_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
    GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStructure);

    SPIx_CS_HIGH(); // 拉高CS引脚(默认不选中从机)
#endif

#ifdef SPI_SLAVE
    // 从机模式配置:CS引脚复用,MISO为推挽输出,其余为输入
    SPIx_CS_AF();   // CS引脚复用为SPI功能

    GPIO_InitStructure.Pins = SPIx_MISO_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // MISO推挽输出
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStructure);

    // SCK、MOSI、CS配置为输入
    GPIO_InitStructure.Pins = SPIx_SCK_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
    GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = SPIx_MOSI_GPIO_PIN;
    GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = SPIx_CS_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP; // CS带上拉输入
    GPIO_Init(SPIx_CS_GPIO_PORT, &GPIO_InitStructure);
#endif

    // 配置LED控制引脚(PB1和PA7)
    GPIO_InitStructure.Pins = GPIO_PIN_1;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.Pins = GPIO_PIN_7;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

    // 初始化LED状态为灭
    PB01_SETLOW(); // PB1输出低电平
    PA07_SETLOW(); // PA7输出低电平
}



DMA 配置函数注释
void DMA_Configuration(void)
{
    DMA_InitTypeDef DMA_InitStructure; // DMA初始化结构体

    // 配置SPI发送DMA通道(内存到外设)
    DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK;         // 块传输模式
    DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT; // 8位数据宽度
    DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Increase; // 内存地址自增
    DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Fix;      // 外设地址固定
    DMA_InitStructure.TrigMode = DMA_HardTrig;             // 硬件触发
    DMA_InitStructure.HardTrigSource = SPIx_DMA_TxTrigSource; // SPI发送触发源
    DMA_InitStructure.DMA_TransferCnt = BufferSize;      // 传输数据量
    DMA_InitStructure.DMA_SrcAddress = (uint32_t)TxBuffer; // 源地址(发送缓冲区)
    DMA_InitStructure.DMA_DstAddress = (uint32_t)&SPIx->DR; // 目标地址(SPI数据寄存器)
    DMA_Init(SPIx_TX_DMACHANNEL, &DMA_InitStructure);      // 初始化DMA通道
    DMA_Cmd(SPIx_TX_DMACHANNEL, ENABLE);                   // 使能DMA通道

    // 配置SPI接收DMA通道(外设到内存)
    DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK;
    DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;
    DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Fix;      // 外设地址固定
    DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Increase; // 内存地址自增
    DMA_InitStructure.TrigMode = DMA_HardTrig;
    DMA_InitStructure.HardTrigSource = SPIx_DMA_RxTrigSource; // SPI接收触发源
    DMA_InitStructure.DMA_TransferCnt = BufferSize;
    DMA_InitStructure.DMA_SrcAddress = (uint32_t)&SPIx->DR;// 源地址(SPI数据寄存器)
    DMA_InitStructure.DMA_DstAddress = (uint32_t)RxBuffer;   // 目标地址(接收缓冲区)
    DMA_Init(SPIx_RX_DMACHANNEL, &DMA_InitStructure);
    DMA_Cmd(SPIx_RX_DMACHANNEL, ENABLE);
}



SPI 配置函数注释
/**
* @brief 配置SPI为16Mbps通信速率
*/
void SPI_Configuration()
{
    SPI_InitTypeDef SPI_InitStructure; // SPI初始化结构体

    // 配置SPI工作参数
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 双线全双工模式
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                      // 主机模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                  // 8位数据帧
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                         // 时钟空闲时为低电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                     // 第一个时钟边沿采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                        // 软件控制片选
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // PCLK/4分频(16MHz)
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;               // MSB优先
    SPI_InitStructure.SPI_Speed = SPI_Speed_High;                      // 高速模式

#ifdef SPI_SLAVE
    // 从机模式特殊配置
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;// 从机模式
    SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;   // 硬件控制片选(通过CS引脚电平)
#endif

    SPI_Init(SPIx, &SPI_InitStructure); // 初始化SPI
    SPI_Cmd(SPIx, ENABLE);            // 使能SPI外设
}



缓冲区比较函数注释
/**
* @brief 比较两个缓冲区内容是否相同
*
* @param pBuffer1 : 第一个缓冲区指针
* @param pBuffer2 : 第二个缓冲区指针
* @param BufferLength : 需要比较的长度
* @return TestStatus
*   @ARG PASSED: 缓冲区内容完全相同
*   @arg FAILED: 缓冲区内容存在差异
*/
TestStatus Buffercmp(uint8_t *pBuffer1, uint8_t *pBuffer2, uint16_t BufferLength)
{
    while (BufferLength--)
    {
      if (*pBuffer1 != *pBuffer2) // 逐字节比较
      {
            return FAILED; // 发现不一致立即返回失败
      }
      pBuffer1++;
      pBuffer2++;
    }
    return PASSED; // 全部比较通过返回成功
}



关键功能说明
SPI模式:通过宏定义选择主机或从机模式,当前配置为主机。
DMA传输:使用DMA通道1和2分别处理SPI的接收与发送,降低CPU负载。
数据校验:通过Buffercmp函数比较发送和接收缓冲区数据的一致性。
硬件指示:通过GPIO控制LED灯显示传输状态(完成/校验成功)。
————————————————
版权声明:本文为CSDN博主「brave and determined」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43260261/article/details/149114026

AdaMaYun 发表于 2025-9-18 09:54

串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信

小小蚂蚁举千斤 发表于 2025-9-23 08:19

串行外设 SPI 速率还是非常快的

jf101 发表于 2025-9-23 15:16

SPI主从通信

中国龙芯CDX 发表于 2025-9-25 14:15

SPI模式:通过宏定义选择主机或从机模式,当前配置为主机。

OKAKAKO 发表于 2025-9-26 15:29

SPI主从通信详解

星辰大海不退缩 发表于 2025-9-27 14:34

串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信。

小夏天的大西瓜 发表于 2025-9-28 14:42

SPI支持主机模式、从机模式
页: [1]
查看完整版本: CW32F030C8T6 SPI主从通信详解