STM32开发(十)STM32F103 通信 —— SPI通信编程详解

文章目录

    • 一、基础知识点
    • 二、开发环境
    • 三、STM32CubeMX相关配置
    • 四、Vscode代码讲解
    • 五、结果演示


一、基础知识点

本实验通过STM32F103 的SPI功能,实现对W25Q64JVSSIQ (Flash芯片)芯片擦除,读数据,写数据等操作。
本实验内容知识点:
1、SPI通信协议介绍
2、闪存 W25Q64JVSS 手册 解析

准备好了吗?开始实战show time。


二、开发环境

1、硬件开发准备
主控:STM32F103ZET6
Flash芯片:W25Q64JVSSIQ
STM32开发(十)STM32F103 通信 —— SPI通信编程详解_第1张图片
F_CLK:PB3     F_MISO:PB4     F_MOSI:PB5     F_CS:PA15

2、软件开发准备
软件开发使用虚拟机 + VScode + STM32Cube 开发STM32,在虚拟机中直接完成编译下载。
该部分可参考:软件开发环境构建


三、STM32CubeMX相关配置

1、STM32CubeMX基本配置
本实验基于CubeMX详解构建基本框架 进行开发。

2、STM32CubeMX SPI相关配置
(1)GPIO配置
STM32开发(十)STM32F103 通信 —— SPI通信编程详解_第2张图片
(2)spi配置
STM32开发(十)STM32F103 通信 —— SPI通信编程详解_第3张图片
spi相关配置需要查看从设备手册,结合配置。


四、Vscode代码讲解

1、flash相关硬件引脚以及控制命令初始化

#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、构建spi相关结构体

// 定义
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基础函数
(1)读写一个字节函数

// 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)写使能、读状态寄存器,根据闪存 W25Q64JVSS 手册 解析中时序编写代码

// 写使能
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、对Flash控制具体函数实现,这部分时序可参考闪存 W25Q64JVSS 手册 解析
(1)读取设备制造商ID、内存类型、容量信息函数: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)擦除扇区空间数据(4K)函数: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)擦除整个芯片函数: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)写入一页数据(256Byte)函数: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)读出不固定长度数据函数: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)写不固定长度数据函数:SPI_WriteDataUnfixed
注:根据手册可知,写flash一次性只能写256字节,对于不固定长度的数据,想要写入flash就要注意数据大小,否则会造成flash页覆盖的现象。

实现思路
flash写不固定长度数据函数思路流程图

具体实现函数

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、主函数中循环写入flash字符串,再读出来。测试时候能否正常实现flash的读写。

    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);

五、结果演示

STM32开发(十)STM32F103 通信 —— SPI通信编程详解_第4张图片

你可能感兴趣的:(STM32开发,stm32,单片机,嵌入式硬件,dsp开发,flash)