星享社 发表于 2025-7-31 22:41

【沁恒CH32V307开发板测评】SPI测试

这次来玩一下CH32V307 SPI接口通信
一、SPI是什么
1、SPI介绍
SPI(Serial Peripheral Interface,串行外设接口)是一种由摩托罗拉公司开发的​​同步串行通信协议​​,专用于短距离设备间的数据交换。它通过​​主从模式​​工作:一个主设备(如CH32V307)控制时钟并发起通信,一个或多个从设备(如传感器、存储器)响应指令。其核心特点是​​全双工通信​​(可同时收发数据)与​​高速传输​​(可达50MHz)
2、工作原理
SPI仅需4根物理连线实现全双工通信:

信号线
作用


​方向​

​SCLK​
主设备输出的同步时钟(速率可配置)
主→从

​MOSI​
主设备发送数据,从设备接收(Master-Out-Slave-In)
主→从

​MISO​
从设备发送数据,主设备接收(Master-In-Slave-Out)
从→主

​CS/SS​
片选信号(低电平激活从设备)
主→从


数据传输原理​​:
主从设备内置​​移位寄存器​​,连接成环形结构。时钟驱动下:

[*]主设备通过MOSI逐位移出数据(如0xAA: 1→0→1→0→1→0→1→0)
[*]从设备通过MISO同步移出数据
[*]8个时钟周期完成1字节交换,形成“数据循环”

SPI通过 ​​CPOL(时钟极性)​​ 和 ​​CPHA(时钟相位)​​ 定义四种模式,决定​​数据采样时机​​
3、优缺点

优点
缺点

速率远超I²C(可达50MHz)
​无硬件应答​:无法自动确认数据接收成功

​全双工通信​:同时收发数据
​引脚占用多​:每增加一个从机需多1根片选线

​硬件简单​:无需上拉电阻,功耗低
​传输距离短​:通常<1米(易受干扰)

​无地址冲突​:通过CS引脚直接选从机
​无标准协议​:不同厂商实现可能不兼容

​灵活数据长度​:支持8/16/32位传输
​单主设备限制:无法多主机协同


二、SPI读写W25Q16测试
1、简单介绍一下W25Q16芯片
W25Q16是一个16M BIT的FLASH芯片,它有8192个可编程页,一个能够写入256个字节;页擦除一次性可以擦除4K/32K/64KB或者整个芯片;分别有512个可擦除扇区,32个可擦除扇区。
它的组成是总共有32个块,每个块有64KB,一个块又分为16个扇区。使用它可以做大容量数据存储。

2、SPI相关引脚初始化


3、SPI初始化
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI3, &SPI_InitStructure);

    SPI_Cmd(SPI3, ENABLE);

4、SPI发送函数
/*********************************************************************
* @fn      SPI3_ReadWriteByte
*
* @brief   SPI3 read or write one byte.
*
* @param   TxData - write one byte data.
*
* @returnRead one byte data.
*/
u8 SPI3_ReadWriteByte(u8 TxData)
{
    while(SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_TXE) == RESET);

    SPI_I2S_SendData(SPI3, TxData);

    while(SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_RXNE) == RESET);

    return SPI_I2S_ReceiveData(SPI3);
}

5、读写FLASH函数
/*********************************************************************
* @fn      SPI_Flash_ReadSR
*
* @brief   Read W25Qxx status register.
*      !!BIT76   5   4   3   2   1   0
*      !!SPR   RVTBBP2 BP1 BP0 WEL BUSY
*
* @returnbyte - status register value.
*/
u8 SPI_Flash_ReadSR(void)
{
    u8 byte = 0;

    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_ReadStatusReg);
    byte = SPI3_ReadWriteByte(0Xff);
    FLASH_CS_HIGH;

    return byte;
}

/*********************************************************************
* @fn      SPI_FLASH_Write_SR
*
* @brief   Write W25Qxx status register.
*
* @param   sr - status register value.
*
* @returnnone
*/
void SPI_FLASH_Write_SR(u8 sr)
{
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_WriteStatusReg);
    SPI3_ReadWriteByte(sr);
    FLASH_CS_HIGH;
}

/*********************************************************************
* @fn      SPI_Flash_Wait_Busy
*
* @brief   Wait flash free.
*
* @returnnone
*/
void SPI_Flash_Wait_Busy(void)
{
    while((SPI_Flash_ReadSR() & 0x01) == 0x01)
      ;
}

/*********************************************************************
* @fn      SPI_FLASH_Write_Enable
*
* @brief   Enable flash write.
*
* @returnnone
*/
void SPI_FLASH_Write_Enable(void)
{
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_WriteEnable);
    FLASH_CS_HIGH;
}

/*********************************************************************
* @fn      SPI_FLASH_Write_Disable
*
* @brief   Disable flash write.
*
* @returnnone
*/
void SPI_FLASH_Write_Disable(void)
{
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_WriteDisable);
    FLASH_CS_HIGH;
}

/*********************************************************************
* @fn      SPI_Flash_ReadID
*
* @brief   Read flash ID.
*
* @returnTemp - FLASH ID.
*/
u16 SPI_Flash_ReadID(void)
{
    u16 Temp = 0;

    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_ManufactDeviceID);
    SPI3_ReadWriteByte(0x00);
    SPI3_ReadWriteByte(0x00);
    SPI3_ReadWriteByte(0x00);
    Temp |= SPI3_ReadWriteByte(0xFF) << 8;
    Temp |= SPI3_ReadWriteByte(0xFF);
    FLASH_CS_HIGH;

    return Temp;
}

/*********************************************************************
* @fn      SPI_Flash_Erase_Sector
*
* @brief   Erase one sector(4Kbyte).
*
* @param   Dst_Addr - 0 !! 2047
*
* @returnnone
*/
void SPI_Flash_Erase_Sector(u32 Dst_Addr)
{
    Dst_Addr *= 4096;
    SPI_FLASH_Write_Enable();
    SPI_Flash_Wait_Busy();
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_SectorErase);
    SPI3_ReadWriteByte((u8)((Dst_Addr) >> 16));
    SPI3_ReadWriteByte((u8)((Dst_Addr) >> 8));
    SPI3_ReadWriteByte((u8)Dst_Addr);
    FLASH_CS_HIGH;
    SPI_Flash_Wait_Busy();
}

/*********************************************************************
* @fn      SPI_Flash_Read
*
* @brief   Read data from flash.
*
* @param   pBuffer -
*          ReadAddr -Initial address(24bit).
*          size - Data length.
*
* @returnnone
*/
void SPI_Flash_Read(u8 *pBuffer, u32 ReadAddr, u16 size)
{
    u16 i;

    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_ReadData);
    SPI3_ReadWriteByte((u8)((ReadAddr) >> 16));
    SPI3_ReadWriteByte((u8)((ReadAddr) >> 8));
    SPI3_ReadWriteByte((u8)ReadAddr);

    for(i = 0; i < size; i++)
    {
      pBuffer = SPI3_ReadWriteByte(0XFF);
    }

    FLASH_CS_HIGH;
}

/*********************************************************************
* @fn      SPI_Flash_Write_Page
*
* @brief   Write data by one page.
*
* @param   pBuffer -
*          WriteAddr - Initial address(24bit).
*          size - Data length.
*
* @returnnone
*/
void SPI_Flash_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 size)
{
    u16 i;

    SPI_FLASH_Write_Enable();
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_PageProgram);
    SPI3_ReadWriteByte((u8)((WriteAddr) >> 16));
    SPI3_ReadWriteByte((u8)((WriteAddr) >> 8));
    SPI3_ReadWriteByte((u8)WriteAddr);

    for(i = 0; i < size; i++)
    {
      SPI3_ReadWriteByte(pBuffer);
    }

    FLASH_CS_HIGH;
    SPI_Flash_Wait_Busy();
}

/*********************************************************************
* @fn      SPI_Flash_Write_NoCheck
*
* @brief   Write data to flash.(need Erase)
*          All data in address rang is 0xFF.
*
* @param   pBuffer -
*          WriteAddr - Initial address(24bit).
*          size - Data length.
*
* @returnnone
*/
void SPI_Flash_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 size)
{
    u16 pageremain;

    pageremain = 256 - WriteAddr % 256;

    if(size <= pageremain)
      pageremain = size;

    while(1)
    {
      SPI_Flash_Write_Page(pBuffer, WriteAddr, pageremain);

      if(size == pageremain)
      {
            break;
      }
      else
      {
            pBuffer += pageremain;
            WriteAddr += pageremain;
            size -= pageremain;

            if(size > 256)
                pageremain = 256;
            else
                pageremain = size;
      }
    }
}

/*********************************************************************
* @fn      SPI_Flash_Write
*
* @brief   Write data to flash.(no need Erase)
*
* @param   pBuffer -
*          WriteAddr - Initial address(24bit).
*          size - Data length.
*
* @returnnone
*/
void SPI_Flash_Write(u8 *pBuffer, u32 WriteAddr, u16 size)
{
    u32 secpos;
    u16 secoff;
    u16 secremain;
    u16 i;

    secpos = WriteAddr / 4096;
    secoff = WriteAddr % 4096;
    secremain = 4096 - secoff;

    if(size <= secremain)
      secremain = size;

    while(1)
    {
      SPI_Flash_Read(SPI_FLASH_BUF, secpos * 4096, 4096);

      for(i = 0; i < secremain; i++)
      {
            if(SPI_FLASH_BUF != 0XFF)
                break;
      }

      if(i < secremain)
      {
            SPI_Flash_Erase_Sector(secpos);

            for(i = 0; i < secremain; i++)
            {
                SPI_FLASH_BUF = pBuffer;
            }

            SPI_Flash_Write_NoCheck(SPI_FLASH_BUF, secpos * 4096, 4096);
      }
      else
      {
            SPI_Flash_Write_NoCheck(pBuffer, WriteAddr, secremain);
      }

      if(size == secremain)
      {
            break;
      }
      else
      {
            secpos++;
            secoff = 0;

            pBuffer += secremain;
            WriteAddr += secremain;
            size -= secremain;

            if(size > 4096)
            {
                secremain = 4096;
            }
            else
            {
                secremain = size;
            }
      }
    }
}

/*********************************************************************
* @fn      SPI_Flash_Erase_Chip
*
* @brief   Erase all FLASH pages.
*
* @returnnone
*/
void SPI_Flash_Erase_Chip(void)
{
    SPI_FLASH_Write_Enable();
    SPI_Flash_Wait_Busy();
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_ChipErase);
    FLASH_CS_HIGH;
    SPI_Flash_Wait_Busy();
}

/*********************************************************************
* @fn      SPI_Flash_PowerDown
*
* @brief   Enter power down mode.
*
* @returnnone
*/
void SPI_Flash_PowerDown(void)
{
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_PowerDown);
    FLASH_CS_HIGH;
    Delay_Us(3);
}

/*********************************************************************
* @fn      SPI_Flash_WAKEUP
*
* @brief   Power down wake up.
*
* @returnnone
*/
void SPI_Flash_WAKEUP(void)
{
    FLASH_CS_LOW;
    SPI3_ReadWriteByte(W25X_ReleasePowerDown);
    FLASH_CS_HIGH;
    Delay_Us(3);
}      6、头文件
/* Winbond SPIFalsh ID */
#define W25Q80                   0XEF13
#define W25Q16                   0XEF14
#define W25Q32                   0XEF15
#define W25Q64                   0XEF16
#define W25Q128                  0XEF17

/* Winbond SPIFalsh Instruction List */
#define W25X_WriteEnable         0x06
#define W25X_WriteDisable      0x04
#define W25X_ReadStatusReg       0x05
#define W25X_WriteStatusReg      0x01
#define W25X_ReadData            0x03
#define W25X_FastReadData      0x0B
#define W25X_FastReadDual      0x3B
#define W25X_PageProgram         0x02
#define W25X_BlockErase          0xD8
#define W25X_SectorErase         0x20
#define W25X_ChipErase         0xC7
#define W25X_PowerDown         0xB9
#define W25X_ReleasePowerDown    0xAB
#define W25X_DeviceID            0xAB
#define W25X_ManufactDeviceID    0x90
#define W25X_JedecDeviceID       0x9F

#define FLASH_CS_HIGH GPIO_WriteBit(GPIOE, GPIO_Pin_6, 1)
#define FLASH_CS_LOWGPIO_WriteBit(GPIOE, GPIO_Pin_6, 0)

u8 SPI1_ReadWriteByte(u8 TxData);
void SPI_Flash_Init(void);
u8 SPI_Flash_ReadSR(void);
void SPI_FLASH_Write_SR(u8 sr);
void SPI_Flash_Wait_Busy(void);
void SPI_FLASH_Write_Enable(void);
void SPI_FLASH_Write_Disable(void);
u16 SPI_Flash_ReadID(void);
void SPI_Flash_Erase_Sector(u32 Dst_Addr);
void SPI_Flash_Read(u8 *pBuffer, u32 ReadAddr, u16 size);
void SPI_Flash_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Write(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Erase_Chip(void);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);   7、主函数
/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @returnnone
*/
int main(void)
{
        u8readData;
    u16 Flash_Model;
    u8 writeData = {0};
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        SystemCoreClockUpdate();
        Delay_Init();
        USART_Printf_Init(115200);       
        printf("SystemClk:%d\r\n",SystemCoreClock);
        printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
        printf("This is printf example\r\n");
           SPI_Flash_Init();

    Flash_Model = SPI_Flash_ReadID();
    for(uint16_t i = 0;i < SIZE;i++) {
      writeData = i;
    }

    switch(Flash_Model)
    {
      case W25Q80:
            printf("W25Q80 OK!\r\n");

            break;

      case W25Q16:
            printf("W25Q16 OK!\r\n");

            break;

      case W25Q32:
            printf("W25Q32 OK!\r\n");

            break;

      case W25Q64:
            printf("W25Q64 OK!\r\n");

            break;

      case W25Q128:
            printf("W25Q128 OK!\r\n");

            break;

      default:
            printf("Fail!\r\n");

            break;
    }
    printf("Start Erase W25Qxx....\r\n");
    SPI_Flash_Erase_Sector(0);
    printf("W25Qxx Erase Finished!\r\n");

    Delay_Ms(500);
    printf("Start Read W25Qxx....\r\n");
    SPI_Flash_Read(readData, 0x0, SIZE);
    printf("read OK\r\n");

    Delay_Ms(500);
    printf("Start Write W25Qxx....\r\n");
    printf("W25Qxx Write Finished!\r\n");
    for(uint8_t i = 0;i < SIZE;i++) {
      printf("%d ",writeData);
      if (i % 50 == 0) {
            printf("\r\n");
      }
    }
    SPI_Flash_Write((u8 *)writeData, 0, SIZE);

   

    Delay_Ms(500);
    printf("Start Read W25Qxx....\r\n");
    SPI_Flash_Read(readData, 0x0, SIZE);
    printf("read OK\r\n");
    for(uint8_t i = 0;i < SIZE;i++) {
      printf("%d ",readData);
      if (i % 50 == 0) {
            printf("\r\n");
      }
    }
   

    Delay_Ms(500);
    printf("Start erase chip....\r\n");
    SPI_Flash_Erase_Chip();
    printf("chip erased finsh\r\n");

    printf("Start Read W25Qxx....\r\n");
    SPI_Flash_Read(readData, 0x0, SIZE);
    printf("%s\r\n", readData);

        while(1)
    {

        }
}
      8、下载验证
主要做了以下测试,擦除扇区、读写数据、擦除整个芯片,擦除整个芯片耗时6ms,这速度擦除还是可以的




丙丁先生 发表于 2025-8-1 22:10

板子照片?

丙丁先生 发表于 2025-8-1 22:11

沁恒CH32V307开发板 有两种

蚊子的噩梦 发表于 2025-8-2 13:13

这篇测评很详细,特别是SPI接口的工作原理和优缺点分析,让我对SPI有了更深的理解。

星享社 发表于 2025-8-2 16:51

丙丁先生 发表于 2025-8-1 22:11
沁恒CH32V307开发板 有两种

板子照片就没有拍了,用的是沁恒CH32V307VCT6-EVT-R2开发板

星享社 发表于 2025-8-2 16:51

蚊子的噩梦 发表于 2025-8-2 13:13
这篇测评很详细,特别是SPI接口的工作原理和优缺点分析,让我对SPI有了更深的理解。
...

多谢大佬鼓励
页: [1]
查看完整版本: 【沁恒CH32V307开发板测评】SPI测试