国产MCU学习Day7——CW32F030C8T6 SPI主从通信详解
每日更新教程,评论区答疑解惑,小白也能变大神!"
目录
一.CW32F030C8T6 SPI 功能
二.SPI主从通信示例(带DMA)
宏定义部分
枚举与全局变量
主函数
时钟配置函数
GPIO 配置函数注释
DMA 配置函数注释
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)
以下是为代码添加的详细注释,以增强可读性和理解性:
宏定义部分
#define SPI_MASTER //主机模式
//#define SPI_SLAVE //从机模式(当前注释掉,表示未启用)// SPI模块配置
#define SPIx CW_SPI2 //使用SPI2模块
#define SPIx_CLK RCC_APB1_PERIPH_SPI2 //SPI2时钟源
#define SPIx_APBClkENx RCC_APBPeriphClk_Enable1 //APB时钟使能函数// SPI引脚配置(SCK、MISO、MOSI、CS)
#define SPIx_SCK_GPIO_CLK RCC_AHB_PERIPH_GPIOA //SCK引脚时钟
#define SPIx_SCK_GPIO_PORT CW_GPIOA //SCK引脚端口
#define SPIx_SCK_GPIO_PIN GPIO_PIN_2 //SCK引脚号
#define SPIx_SCK_AF() PA02_AFx_SPI2SCK() //SCK复用功能配置#define SPIx_MISO_GPIO_CLK RCC_AHB_PERIPH_GPIOA //MISO引脚时钟
#define SPIx_MISO_GPIO_PORT CW_GPIOA //MISO引脚端口
#define SPIx_MISO_GPIO_PIN GPIO_PIN_0 //MISO引脚号
#define SPIx_MISO_AF() PA00_AFx_SPI2MISO() //MISO复用功能配置#define SPIx_MOSI_GPIO_CLK RCC_AHB_PERIPH_GPIOA //MOSI引脚时钟
#define SPIx_MOSI_GPIO_PORT CW_GPIOA //MOSI引脚端口
#define SPIx_MOSI_GPIO_PIN GPIO_PIN_1 //MOSI引脚号
#define SPIx_MOSI_AF() PA01_AFx_SPI2MOSI() //MOSI复用功能配置#define SPIx_CS_GPIO_CLK RCC_AHB_PERIPH_GPIOA //CS引脚时钟
#define SPIx_CS_GPIO_PORT CW_GPIOA //CS引脚端口
#define SPIx_CS_GPIO_PIN GPIO_PIN_3 //CS引脚号
#define SPIx_CS_AF() PA03_AFx_SPI2CS() //CS复用功能配置// CS引脚电平控制
#define SPIx_CS_LOW() PA03_SETLOW() //拉低CS(选中从机)
#define SPIx_CS_HIGH() PA03_SETHIGH() //拉高CS(取消选中)// DMA配置
#define SPIx_RX_DMACHANNEL CW_DMACHANNEL1 //RX DMA通道
#define SPIx_TX_DMACHANNEL CW_DMACHANNEL2 //TX DMA通道
#define SPIx_DMA_RxTrigSource DMA_HardTrig_SPI2_RXBufferNE //RX触发源
#define SPIx_DMA_TxTrigSource DMA_HardTrig_SPI2_TXBufferE //TX触发源
#define BufferSize 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[BufferSize]; //接收缓冲区
volatile TestStatus TransferStatus = FAILED; //传输状态标志
主函数
int32_t main(void)
{RCC_Configuration(); //初始化系统时钟和外设时钟GPIO_Configuration(); //配置GPIO引脚和复用功能DMA_Configuration(); //配置DMA通道和触发源SPI_Configuration(); //配置SPI模块参数(模式、速率等)#ifdef SPI_MASTERSPIx_CS_LOW(); //主机模式下拉低CS,选中从机
#endif#ifdef SPI_SLAVESPI_FlushSendBuff(SPIx); //从机模式下清空发送缓冲区
#endifSPI_DMACmd(SPIx, SPI_DMAReq_Tx | SPI_DMAReq_Rx, ENABLE); //启用SPI DMA传输while (1){if (RxBuffer[BufferSize - 1] == 0x55) //检测接收完成标志{
#ifdef SPI_MASTERSPIx_CS_HIGH(); //主机模式下释放CS
#endifPB01_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引脚电平)
#endifSPI_Init(SPIx, &SPI_InitStructure); // 初始化SPISPI_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灯显示传输状态(完成/校验成功)。