STM32 development (10) STM32F103 communication - detailed explanation of SPI communication programming


1. Basic knowledge points

In this experiment, through the SPI function of STM32F103, operations such as erasing, reading data, and writing data on the W25Q64JVSSIQ (Flash chip) chip are realized.
Knowledge points of this experiment:
1. Introduction to SPI communication protocol
2. Analysis of flash memory W25Q64JVSS manual

Are you ready? Start actual combat show time.


2. Development environment

1. Preparation for hardware development
Main control: STM32F103ZET6
Flash chip: W25Q64JVSSIQ
insert image description here
F_CLK: PB3 F_MISO: PB4 F_MOSI: PB5 F_CS: PA15

2. Software development preparation
Software development uses virtual machine + VScode + STM32Cube to develop STM32, and compile and download directly in the virtual machine.
This part can refer to: Software Development Environment Construction


3. STM32CubeMX related configuration

1. Basic configuration of STM32CubeMX
This experiment is developed based on the basic framework of CubeMX detailed explanation .

2. STM32CubeMX SPI related configuration
(1) GPIO configuration
insert image description here
(2) spi configuration
insert image description here
spi related configuration needs to be viewed in the slave device manual and combined with the configuration.


Four, Vscode code explanation

1. Flash related hardware pins and control command initialization

#define Myspi_Flash_CS_Enable     HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port, SPI_Flash_CS_Pin, GPIO_PIN_RESET);
#define Myspi_Flash_CS_Disable    HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port, SPI_Flash_CS_Pin, GPIO_PIN_SET);

#define W25_Write_Enable          0x06
#define W25_Write_Disable         0x04
#define W25_Read_Status_Register  0x05
#define W25_Read_Device_ID        0x9F
#define W25_Sector_Erase          0x20
#define W25_Chip_Erase            0xc7
#define W25_Read_Data             0x03
#define W25_Page_Program          0x02

#define SPI_FLASH_PageSize        256

2. Build spi-related structures

// 定义
typedef struct Myspi_s
{
    
    
  void (*SPI_Read_Device_ID)(void);
  void (*SPI_EraseSector)(uint32_t);
  void (*SPI_ChipSector)(void);
  void (*SPI_ReadDataUnfixed)(uint32_t, uint8_t*, uint16_t );
  void (*SPI_WriteDataUnfixed)(uint32_t, uint8_t*, uint16_t);
} Myspi_t;

// 下面详细讲解
static void SPI_Read_Device_ID(void);
static void SPI_EraseSector(uint32_t);
static void SPI_ChipSector(void);
static void SPI_ReadDataUnfixed(uint32_t ReadAddr, uint8_t* pReadBuffer, uint16_t len);
static void SPI_WriteDataUnfixed(uint32_t WriteAddr, uint8_t* pWriteBuffer, uint16_t len);

// 初始化
Myspi_t Myspi = {
    
    
  SPI_Read_Device_ID,
  SPI_EraseSector,
  SPI_ChipSector,
  SPI_ReadDataUnfixed,
  SPI_WriteDataUnfixed
};

3. spi basic function
(1) read and write a byte function

// SPI读一个字节
static uint8_t SPI_Read_One_Byte(void)
{
    
    
  uint8_t Recvice_Byte;
  
// 读取1个字节
  if( HAL_OK != (HAL_SPI_Receive(&hspi3, &Recvice_Byte, 1, 0x0A) ))        
    Recvice_Byte = 0xFF;
    
// 返回读取到的值
  return Recvice_Byte;                        
}

// SPI写一个字节
static void SPI_Write_One_Byte(uint8_t data)
{
    
    
  uint8_t tmp_data = data;
  // 写一个字节
  HAL_SPI_Transmit(&hspi3, &tmp_data, 1, 0x0A);       
}

(2) Write enable and read status registers, and write codes according to the timing sequence in the analysis of the flash memory W25Q64JVSS manual

// 写使能
static void SPI_Flash_WriteEnable()
{
    
    
  //片选拉低使能
  Myspi_Flash_CS_Enable;
  //发送命令:写使能
  SPI_Write_One_Byte(W25_Write_Enable);
  //片选拉高释放
  Myspi_Flash_CS_Disable;
}


// 读状态寄存器,判断是否处于忙碌状态
static void SPI_Read_Flash_Status(void)
{
    
    
  uint8_t Chip_Status;
  
// 片选拉低使能
  Myspi_Flash_CS_Enable;  
          
// 发送读取状态寄存器地址0x05
  SPI_Write_One_Byte(W25_Read_Status_Register);   

  Timer6.uRun_Timer = 0;

  do
  {
    
    
 // 读取芯片状态值
    Chip_Status = SPI_Read_One_Byte();               
 // 2s监测时间 
    if ( (Timer6.uRun_Timer++) >= 2000 ) break;   
  } 
  while ( (Chip_Status & BIT0) == BIT0);              // 判断芯片是否处于忙碌状态
  
// 片选拉高释放
  Myspi_Flash_CS_Disable;       
}

4. Realize the specific functions of Flash control. For this part of the timing, please refer to the flash memory W25Q64JVSS manual analysis
(1) Read device manufacturer ID, memory type, capacity information function: SPI_Read_Device_ID

static void SPI_Read_Device_ID(void)
{
    
    
  uint8_t buf[3];
  uint32_t Device_Type;
  
  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();
  
  //片选拉低使能
  Myspi_Flash_CS_Enable;

  //发送命令:读取JEDEC ID(设备标识符 -> 制造商+内存类型+容量)
  SPI_Write_One_Byte(W25_Read_Device_ID);
  buf[0] = SPI_Read_One_Byte();
  buf[1] = SPI_Read_One_Byte();
  buf[2] = SPI_Read_One_Byte();

  //片选拉高释放
  Myspi_Flash_CS_Disable;
  
  Device_Type = (buf[0] << 16) + (buf[1] << 8) + buf[2];
  printf(" ID of SPI flash is 0x%x\r\n",Device_Type);
  
  switch(Device_Type)
  {
    
    
    case 0xEF4015: printf("Flash芯片型号为W25Q16JV-IQ/JQ, 16M-bit /2M-byte\r\n"); break;
    case 0xEF4017: printf("Flash芯片型号为W25Q64JV-IQ/JQ, 64M-bit /8M-byte\r\n"); break;  
    case 0xEF4018: printf("Flash芯片型号为W25Q128JV-IQ/JQ,128M-bit/16M-byte\r\n"); break;
    default: printf("Flash芯片型号未知\r\n"); 
  }
}

(2) Erase sector space data (4K) function: SPI_EraseSector

static void SPI_EraseSector(uint32_t SectorAddr)
{
    
    
  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();

  // 写使能,允许芯片擦除
  SPI_Flash_WriteEnable();

  //片选拉低使能
  Myspi_Flash_CS_Enable;

  //发送命令擦除指令
  SPI_Write_One_Byte(W25_Sector_Erase);
  //发送要擦除的地址
  //地址高8位
  SPI_Write_One_Byte((SectorAddr & 0xFF0000) >> 16);
  //地址中8位
  SPI_Write_One_Byte((SectorAddr & 0xFF00) >> 8);
  //地址低8位
  SPI_Write_One_Byte(SectorAddr & 0xFF);

  //片选拉高释放
  Myspi_Flash_CS_Disable;

  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();
}

(3) Erase the entire chip function: SPI_ChipSector

static void SPI_ChipSector(void)
{
    
    
  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();

  // 写使能,允许芯片擦除
  SPI_Flash_WriteEnable();

  //片选拉低使能
  Myspi_Flash_CS_Enable;

  //发送命令擦除指令
  SPI_Write_One_Byte(W25_Chip_Erase);

  //片选拉高释放
  Myspi_Flash_CS_Disable;

  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();

}

(4) Write a page of data (256Byte) function: SPI_Page_Program

static void SPI_Page_Program(uint32_t WriteAddr, uint8_t* pWriteBuffer, uint16_t len)
{
    
    
  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();

  // 写使能,允许芯片擦除
  SPI_Flash_WriteEnable();

  //片选拉低使能
  Myspi_Flash_CS_Enable;

  //发送页编程命令
  SPI_Write_One_Byte(W25_Page_Program);

  //发送要读取的数据起始地址
  //地址高8位
  SPI_Write_One_Byte((WriteAddr & 0xFF0000) >> 16);
  //地址中8位
  SPI_Write_One_Byte((WriteAddr & 0xFF00) >> 8);
  //地址低8位
  SPI_Write_One_Byte(WriteAddr & 0xFF);

  if(len > SPI_FLASH_PageSize)
  {
    
    
    len = SPI_FLASH_PageSize;
    printf("Error: Flash每次写入数据不能超过256字节! \n");
  }
  // 写入数据
  while(len--)
  {
    
    
    SPI_Write_One_Byte(*pWriteBuffer);
    pWriteBuffer++;
  }

  //片选拉高释放
  Myspi_Flash_CS_Disable;

  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();
}

(5) Read unfixed length data function: SPI_ReadDataUnfixed

static void SPI_ReadDataUnfixed(uint32_t ReadAddr, uint8_t* pReadBuffer, uint16_t len)
{
    
    
  //检测flash是否处于忙碌状态
  SPI_Read_Flash_Status();

  //片选拉低使能
  Myspi_Flash_CS_Enable;

  //发送命令读取数据
  SPI_Write_One_Byte(W25_Read_Data);

  //发送要读取的数据起始地址
  //地址高8位
  SPI_Write_One_Byte((ReadAddr & 0xFF0000) >> 16);
  //地址中8位
  SPI_Write_One_Byte((ReadAddr & 0xFF00) >> 8);
  //地址低8位
  SPI_Write_One_Byte(ReadAddr & 0xFF);

  // 读数据
  while(len--)
  {
    
    
    *pReadBuffer = SPI_Read_One_Byte();
    pReadBuffer++;
  }

  //片选拉高释放
  Myspi_Flash_CS_Disable;
}

(6) Function for writing unfixed-length data: SPI_WriteDataUnfixed
Note: According to the manual, only 256 bytes can be written at a time when writing flash. Page overlay phenomenon.

Flowchart of ideas for implementing
flash write variable-length data functions

Concrete implementation function

static void SPI_WriteDataUnfixed(uint32_t WriteAddr, uint8_t* pWriteBuffer, uint16_t len)
{
    
    
  uint32_t PageNum                  = len / SPI_FLASH_PageSize;                     //写入页数
  uint8_t  NotEnoughNum             = len % SPI_FLASH_PageSize;                     //不足一页的数量
  uint8_t  WriteAddrPageAlignment   = WriteAddr % SPI_FLASH_PageSize;               //如果取余为0,则地址页对齐,可以写连续写入256字节
  uint8_t  NotAlignmentNumofPage    = SPI_FLASH_PageSize - WriteAddrPageAlignment;  //地址不对齐部分,最多可以写入的字节数

  // 判断是否地址对齐
  if( WriteAddrPageAlignment == 0)
  {
    
    
    // 判断写入数据是否超出256Byte
    if( PageNum == 0 )
    {
    
    
      // 没超出256Byte,直接页写入
      SPI_Page_Program(WriteAddr, pWriteBuffer, len);   
    }
    else
    {
    
    
      // 超出256Byte,先写入一页
      while(PageNum--)
      {
    
    
        SPI_Page_Program(WriteAddr, pWriteBuffer, SPI_FLASH_PageSize); 
        // 准备下一页要写入的地址和数据   
        WriteAddr = WriteAddr + SPI_FLASH_PageSize;         
        pWriteBuffer = pWriteBuffer + SPI_FLASH_PageSize;
      }

      // 判断是否存在不足一页的数据
      if(NotEnoughNum > 0)
      {
    
    
        SPI_Page_Program(WriteAddr, pWriteBuffer, NotEnoughNum); 
      }
    }
  }
  else
  {
    
    
    // 判断写入数据是否超出256Byte
    if( PageNum == 0 )
    {
    
    
      // 判断不足256字节的数据是否超出地址不对齐部分
      if( NotEnoughNum <= NotAlignmentNumofPage )
      {
    
    
        SPI_Page_Program(WriteAddr, pWriteBuffer, len); 
      }
      else
      {
    
    
        SPI_Page_Program(WriteAddr, pWriteBuffer, NotAlignmentNumofPage); 
        pWriteBuffer = pWriteBuffer + NotAlignmentNumofPage;
        WriteAddr    = WriteAddr + NotAlignmentNumofPage;

        //写入超出部分数据
        SPI_Page_Program(WriteAddr, pWriteBuffer, NotEnoughNum-NotAlignmentNumofPage); 
      }
    }
    else
    {
    
    
      //先写地址不对齐部分允许写入的最大长度,地址此时对齐了
      SPI_Page_Program(WriteAddr,pWriteBuffer,NotAlignmentNumofPage);       
      pWriteBuffer += NotAlignmentNumofPage;
      WriteAddr    += NotAlignmentNumofPage;  

      //地址对其后,重新计算写入页数与不足一页的数量
      len           -= NotAlignmentNumofPage;
      PageNum       = len / SPI_FLASH_PageSize;            //待写入页数
      NotEnoughNum  = len % SPI_FLASH_PageSize; 
      
      //先写入整页
      while(PageNum--)
      {
    
    
        SPI_Page_Program(WriteAddr,pWriteBuffer,SPI_FLASH_PageSize);
        pWriteBuffer += SPI_FLASH_PageSize;
        WriteAddr    += SPI_FLASH_PageSize;
      }
      //再写入不足一页的数据
      if(NotEnoughNum > 0)
      {
    
    
        SPI_Page_Program(WriteAddr,pWriteBuffer,NotEnoughNum);
      }
    }
  }
}

5. In the main function, the flash string is cyclically written, and then read out. Whether the flash can be read and written normally during the test.

    uint8_t i;
    uint8_t Flash_Flag = TRUE;
    uint8_t Tx_Buffer[] = "BWD laboratory - SPI Flash Test";
    const uint8_t BufferSize  = sizeof(Tx_Buffer)/sizeof(Tx_Buffer[0]);
    uint8_t Rx_Buffer[BufferSize];
    
    //扇区擦除
    Myspi.SPI_EraseSector(0x00001000);

    //写入不定长度数据
    Myspi.SPI_WriteDataUnfixed(0x00001024, Tx_Buffer, BufferSize);
    printf("写入的数据为:%s\r\n", Tx_Buffer);
    //读出不定长数据
    Myspi.SPI_ReadDataUnfixed(0x0001024, Rx_Buffer, BufferSize);
    printf("读出的数据为:%s\r\n", Rx_Buffer);
    //比较缓存数据
    for(i=0;i<BufferSize;i++)
    {
    
    
        if(Tx_Buffer[i] != Rx_Buffer[i])
        {
    
    
            Flash_Flag = FALSE;
            break;
        }
    }
    //打印比较结果
    if(Flash_Flag == TRUE)
        printf("BWD ---- Falsh芯片读写测试成功! \r\n");
    else
        printf("BWD ---- Falsh芯片读写测试失败! \r\n");
    
    //延时1s
    HAL_Delay(1000);

5. Results demonstration

insert image description here

Guess you like

Origin blog.csdn.net/weixin_43564241/article/details/129900600