SPI通信

本文档详细介绍了基于STM32的SPI外设与W25Q128串行Flash的交互,包括初始化、读写操作、扇区擦除和整片擦除等功能,还展示了读取Flash型号ID和设备ID的方法。同时,提供了读写整数和小数的代码示例,以及读取磁编码器角度值的实现。
摘要由CSDN通过智能技术生成

1.SPI与外部flash通信实验。

spi.h

#ifndef __BSP_SPIFLASH_H__
#define __BSP_SPIFLASH_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
//#define  SPI_FLASH_ID                       0x3015     //W25X16   2MB
//#define  SPI_FLASH_ID                       0x4015	   //W25Q16   4MB
//#define  SPI_FLASH_ID                       0X4017     //W25Q64   8MB
#define  SPI_FLASH_ID                       0X4018     //W25Q128  16MB YS-F4Pro开发默认使用

#define FLASH_SPIx                                 SPI2
#define FLASH_SPIx_RCC_CLK_ENABLE()                __HAL_RCC_SPI2_CLK_ENABLE()
#define FLASH_SPIx_RCC_CLK_DISABLE()               __HAL_RCC_SPI2_CLK_DISABLE()

#define FLASH_SPI_GPIO_ClK_ENABLE()                {__HAL_RCC_GPIOI_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();} 
#define FLASH_SPI_GPIO_PORT                        GPIOI
#define FLASH_SPI_SCK_PIN                          GPIO_PIN_1

#define FLASH_SPI_MOSI_PIN                         GPIO_PIN_3
    
#define FLASH_SPI_CS_PIN                           GPIO_PIN_0

#define FLASH_SPI_CS_ENABLE()                      HAL_GPIO_WritePin(FLASH_SPI_GPIO_PORT, FLASH_SPI_CS_PIN, GPIO_PIN_RESET)
#define FLASH_SPI_CS_DISABLE()                     HAL_GPIO_WritePin(FLASH_SPI_GPIO_PORT, FLASH_SPI_CS_PIN, GPIO_PIN_SET)

#define FLASH_SPI_MISO_PORT                        GPIOC
#define FLASH_SPI_MISO_PIN                         GPIO_PIN_2


/* 扩展变量 ------------------------------------------------------------------*/
extern SPI_HandleTypeDef hspiflash;

/* 函数声明 ------------------------------------------------------------------*/

void MX_SPIFlash_Init(void);
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
uint32_t SPI_FLASH_ReadID(void);
uint32_t SPI_FLASH_ReadDeviceID(void);
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);

uint8_t SPI_FLASH_ReadByte(void);
uint8_t SPI_FLASH_SendByte(uint8_t byte);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);

#endif  /* __BSP_SPIFLASH_H__ */

spi.c

#include "spiflash/bsp_spiflash.h"
#include "usart/bsp_debug_usart.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
/* 指令的含义要看手册 */
#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256
#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 WIP_Flag                        0x01  /* Write In Progress (WIP) flag */

#define Dummy_Byte                      0xFF

/* 私有变量 ------------------------------------------------------------------*/
SPI_HandleTypeDef hspiflash;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/

/**
  * 函数功能: 串行FLASH初始化
  * 输入参数: huart:串口句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
*/
void MX_SPIFlash_Init(void)
{
  hspiflash.Instance = FLASH_SPIx;
  hspiflash.Init.Mode = SPI_MODE_MASTER;//主模式
  hspiflash.Init.Direction = SPI_DIRECTION_2LINES;//双向(两根线)
  hspiflash.Init.DataSize = SPI_DATASIZE_8BIT;     //8位数据帧长度
  hspiflash.Init.CLKPolarity = SPI_POLARITY_LOW;   //时钟极性低
  hspiflash.Init.CLKPhase = SPI_PHASE_1EDGE;//奇数位  时钟相位
  hspiflash.Init.NSS = SPI_NSS_SOFT;//软件片选
  hspiflash.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;//预分频系数为4
  hspiflash.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行
  hspiflash.Init.TIMode = SPI_TIMODE_DISABLE;
  hspiflash.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//CRC校验失能
  hspiflash.Init.CRCPolynomial = 10;
  HAL_SPI_Init(&hspiflash);
}

/**
  * 函数功能: SPI外设系统级初始化
  * 输入参数: hspi:SPI句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  if(hspi->Instance==FLASH_SPIx)
  {
    /* SPI外设时钟使能 */
    FLASH_SPIx_RCC_CLK_ENABLE();
    /* GPIO外设时钟使能 */
    FLASH_SPI_GPIO_ClK_ENABLE();
    
    /**SPI2 GPIO Configuration    
    PI0     ------> SPI2_NSS
    PI1     ------> SPI2_SCK
    PC2     ------> SPI2_MISO
    PI3     ------> SPI2_MOSI 
    */
    GPIO_InitStruct.Pin = FLASH_SPI_SCK_PIN|FLASH_SPI_MOSI_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(FLASH_SPI_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = FLASH_SPI_MISO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStruct);
    
    HAL_GPIO_WritePin(FLASH_SPI_GPIO_PORT, FLASH_SPI_CS_PIN, GPIO_PIN_SET);
    GPIO_InitStruct.Pin = FLASH_SPI_CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(FLASH_SPI_GPIO_PORT, &GPIO_InitStruct);
  }
}

/**
  * 函数功能: SPI外设系统级反初始化
  * 输入参数: hspi:SPI句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* hspi)
{

  if(hspi->Instance==FLASH_SPIx)
  {
    /* SPI外设时钟禁用 */
    FLASH_SPIx_RCC_CLK_DISABLE();
  
    /**SPI2 GPIO Configuration    
    PI0     ------> SPI2_NSS
    PI1     ------> SPI2_SCK
    PC2     ------> SPI2_MISO
    PI3     ------> SPI2_MOSI 
    */
    HAL_GPIO_DeInit(FLASH_SPI_GPIO_PORT, FLASH_SPI_SCK_PIN|FLASH_SPI_MOSI_PIN|FLASH_SPI_CS_PIN);
    HAL_GPIO_DeInit(FLASH_SPI_MISO_PORT, FLASH_SPI_MISO_PIN);
  }
} 


/**
  * 函数功能: 擦除扇区
  * 输入参数: SectorAddr:待擦除扇区地址,要求为4096倍数
  * 返 回 值: 无
  * 说    明:串行Flash最小擦除块大小为4KB(4096字节),即一个扇区大小,要求输入参数
  *           为4096倍数。在往串行Flash芯片写入数据之前要求先擦除空间。
  */
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();
  SPI_FLASH_WaitForWriteEnd();
  /* 擦除扇区 */
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 发送扇区擦除指令*/
  SPI_FLASH_SendByte(W25X_SectorErase);
  /*发送擦除扇区地址的高位*/
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  /* 发送擦除扇区地址的中位 */
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  /* 发送擦除扇区地址的低位 */
  SPI_FLASH_SendByte(SectorAddr & 0xFF);
  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();
  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 擦除整片
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:擦除串行Flash整片空间
  */
void SPI_FLASH_BulkErase(void)
{
 /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

  /* 整片擦除 Erase */
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 发送整片擦除指令*/
  SPI_FLASH_SendByte(W25X_ChipErase);
  /* 禁用串行FLASH: CS高电平 */
  FLASH_SPI_CS_DISABLE();

  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 往串行FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
  * 输入参数: pBuffer:待写入数据的指针
  *           WriteAddr:写入地址
  *           NumByteToWrite:写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize
  * 返 回 值: 无
  * 说    明:串行Flash每页大小为256个字节
  */
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

   /* 寻找串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 写送写指令*/
  SPI_FLASH_SendByte(W25X_PageProgram);
  /*发送写地址的高位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  /*发送写地址的中位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  /*发送写地址的低位*/
  SPI_FLASH_SendByte(WriteAddr & 0xFF);

  if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
  {
     NumByteToWrite = SPI_FLASH_PerWritePageSize;
     //printf("Err: SPI_FLASH_PageWrite too large!\n");
  }

  /* 写入数据*/
  while (NumByteToWrite--)
  {
     /* 发送当前要写入的字节数据 */
    SPI_FLASH_SendByte(*pBuffer);
     /* 指向下一字节数据 */
    pBuffer++;
  }

  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();

  /* 等待写入完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 往串行FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * 输入参数: pBuffer:待写入数据的指针
  *           WriteAddr:写入地址
  *           NumByteToWrite:写入数据长度
  * 返 回 值: 无
  * 说    明:该函数可以设置任意写入数据长度
  */
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % SPI_FLASH_PageSize;
  count = SPI_FLASH_PageSize - Addr;
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

  if (Addr == 0) /* 若地址与 SPI_FLASH_PageSize 对齐  */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }

      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else /* 若地址与 SPI_FLASH_PageSize 不对齐 */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
      {
        temp = NumOfSingle - count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;

      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }

      if (NumOfSingle != 0)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

/**
  * 函数功能: 从串行Flash读取数据
  * 输入参数: pBuffer:存放读取到数据的指针
  *           ReadAddr:读取数据目标地址
  *           NumByteToRead:读取数据长度
  * 返 回 值: 无
  * 说    明:该函数可以设置任意读取数据长度
  */
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送 读 指令 */
  SPI_FLASH_SendByte(W25X_ReadData);

  /* 发送 读 地址高位 */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* 发送 读 地址中位 */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* 发送 读 地址低位 */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);

  while (NumByteToRead--) /* 读取数据 */
  {
     /* 读取一个字节*/
    *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
    /* 指向下一个字节缓冲区 */
    pBuffer++;
  }

  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();
}

/**
  * 函数功能: 读取串行Flash型号的ID
  * 输入参数: 无
  * 返 回 值: uint32_t:串行Flash的型号ID
  * 说    明:  FLASH_ID      IC型号      存储空间大小         
                0x3015      W25X16        2M byte
                0x4015	    W25Q16        4M byte
                0X4017      W25Q64        8M byte
                0X4018      W25Q128       16M byte  (YS-F1Pro开发板默认配置)
  */
uint32_t SPI_FLASH_ReadID(void)
{
  uint32_t Temp = 0, Temp1 = 0, Temp2 = 0;

  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:读取芯片型号ID */
  SPI_FLASH_SendByte(W25X_JedecDeviceID);

  /* 从串行Flash读取一个字节数据 */
  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 从串行Flash读取一个字节数据 */
  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 从串行Flash读取一个字节数据 */
  Temp2 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();
  
  Temp = Temp1 << 8 | Temp2;
  return Temp;
}

/**
  * 函数功能: 读取串行Flash设备ID
  * 输入参数: 无
  * 返 回 值: uint32_t:串行Flash的设备ID
  * 说    明:
  */
uint32_t SPI_FLASH_ReadDeviceID(void)
{
  uint32_t Temp = 0;

  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:读取芯片设备ID * */
  SPI_FLASH_SendByte(W25X_DeviceID);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  
  /* 从串行Flash读取一个字节数据 */
  Temp = SPI_FLASH_SendByte(Dummy_Byte);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();

  return Temp;
}

/**
  * 函数功能: 启动连续读取数据串
  * 输入参数: ReadAddr:读取地址
  * 返 回 值: 无
  * 说    明:Initiates a read data byte (READ) sequence from the Flash.
  *           This is done by driving the /CS line low to select the device,
  *           then the READ instruction is transmitted followed by 3 bytes
  *           address. This function exit and keep the /CS line low, so the
  *           Flash still being selected. With this technique the whole
  *           content of the Flash is read with a single READ instruction.
  */
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr)
{
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Read from Memory " instruction */
  SPI_FLASH_SendByte(W25X_ReadData);

  /* Send the 24-bit address of the address to read from -----------------------*/
  /* Send ReadAddr high nibble address byte */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* Send ReadAddr medium nibble address byte */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* Send ReadAddr low nibble address byte */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
}

/**
  * 函数功能: 从串行Flash读取一个字节数据
  * 输入参数: 无
  * 返 回 值: uint8_t:读取到的数据
  * 说    明:This function must be used only if the Start_Read_Sequence
  *           function has been previously called.
  */
uint8_t SPI_FLASH_ReadByte(void)
{
  uint8_t d_read,d_send=Dummy_Byte;
  if(HAL_SPI_TransmitReceive(&hspiflash,&d_send,&d_read,1,0xFFFFFF)!=HAL_OK)
    d_read=Dummy_Byte;
  
  return d_read;    
}

/**
  * 函数功能: 往串行Flash读取写入一个字节数据并接收一个字节数据
  * 输入参数: byte:待发送数据
  * 返 回 值: uint8_t:接收到的数据
  * 说    明:无
  */
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
  uint8_t d_read,d_send=byte;
  if(HAL_SPI_TransmitReceive(&hspiflash,&d_send,&d_read,1,0xFFFFFF)!=HAL_OK)
    d_read=Dummy_Byte;
  
  return d_read; 
}

/**
  * 函数功能: 使能串行Flash写操作
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_FLASH_WriteEnable(void)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:写使能 */
  SPI_FLASH_SendByte(W25X_WriteEnable);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();
}

/**
  * 函数功能: 等待数据写入完成
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:Polls the status of the Write In Progress (WIP) flag in the
  *           FLASH's status  register  and  loop  until write  opertaion
  *           has completed.
  */
void SPI_FLASH_WaitForWriteEnd(void)
{
  uint8_t FLASH_Status = 0;

  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Read Status Register" instruction */
  SPI_FLASH_SendByte(W25X_ReadStatusReg);

  /* Loop as long as the memory is busy with a write cycle */
  do
  {
    /* Send a dummy byte to generate the clock needed by the FLASH
    and put the value of the status register in FLASH_Status variable */
    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);	 
  }
  while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress */

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE();
}


/**
  * 函数功能: 进入掉电模式
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_Flash_PowerDown(void)   
{ 
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Power Down" instruction */
  SPI_FLASH_SendByte(W25X_PowerDown);

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE();
}   

/**
  * 函数功能: 唤醒串行Flash
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_Flash_WAKEUP(void)   
{
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Power Down" instruction */
  SPI_FLASH_SendByte(W25X_ReleasePowerDown);

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE(); 
}   

main.c

#include "stm32f4xx_hal.h"
#include "usart/bsp_debug_usart.h"
#include "spiflash/bsp_spiflash.h"
#include "led/bsp_led.h"

/* 私有类型定义 --------------------------------------------------------------*/
typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;

/* 私有宏定义 ----------------------------------------------------------------*/
/* 获取缓冲区的长度 */
#define countof(a)      (sizeof(a) / sizeof(*(a)))
#define TxBufferSize1   (countof(TxBuffer1) - 1)
#define RxBufferSize1   (countof(TxBuffer1) - 1)
#define BufferSize      (countof(Tx_Buffer)-1)

#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress

/* 私有变量 ------------------------------------------------------------------*/
uint8_t Tx_Buffer[] = "dome good";
uint8_t Rx_Buffer[BufferSize];

__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength);

/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 系统时钟配置
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
  __HAL_RCC_PWR_CLK_ENABLE();                                     //使能PWR时钟

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  //设置调压器输出电压级别1

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                        //打开HSE 
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    //打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            //PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLM = 8;                                 //8分频MHz
  RCC_OscInitStruct.PLL.PLLN = 336;                               //336倍频
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     //2分频,得到168MHz主时钟
  RCC_OscInitStruct.PLL.PLLQ = 7;                                 //USB/SDIO/随机数产生器等的主PLL分频系数
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHz
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

  HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用
  
 	// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                // 配置并启动系统滴答定时器
  /* 系统滴答定时器时钟源 */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* 系统滴答定时器中断优先级配置 */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* LED灯初始化 */
  LED_GPIO_Init();
  
  /* 初始化串口并配置串口中断优先级 */
  MX_DEBUG_USART_Init();
  /* 调用格式化输出函数打印输出数据 */
  printf("这是一个16M byte串行flash(W25Q128)读写测试实验\n"); 
  
  MX_SPIFlash_Init();
  
  /* Get SPI Flash Device ID */
	DeviceID = SPI_FLASH_ReadDeviceID();
  
  HAL_Delay(100);
  
  /* Get SPI Flash ID */
	FlashID = SPI_FLASH_ReadID();
  
  printf("FlashID is 0x%X,  Manufacturer Device ID is 0x%X\n", FlashID, DeviceID);
	
	/* Check the SPI Flash ID */
	if (FlashID == SPI_FLASH_ID)  /* #define  sFLASH_ID  0XEF4018 */
	{	
		printf("检测到华邦串行flash W25Q128 !\n");
		
		/* 擦除SPI的扇区以写入 */
		SPI_FLASH_SectorErase(FLASH_SectorToErase);	 	 
		
		/* 将发送缓冲区的数据写到flash中 */ 	
		SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);
		printf("写入的数据为:\n%s \n", Tx_Buffer);
		
		/* 将刚刚写入的数据读出来放到接收缓冲区中 */
		SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
		printf("读出的数据为:\n %s\n", Rx_Buffer);
		
    
		/* 检查写入的数据与读出的数据是否相等 */
		TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
		
		if( PASSED == TransferStatus1 )
		{    
			printf("16M串行flash(W25Q128)测试成功!\r");
			LED1_ON;
		}
		else
		{        
			printf("16M串行flash(W25Q128)测试失败!\r");
			LED2_ON;
		}
	}
	else
	{    
		printf("获取不到 W25Q128 ID!\n");
		LED3_ON;
	}
  /* 无限循环 */
  while (1)
  {
  }
}

/**
  * 函数功能: 比较两个缓冲区中的数据是否相等
  * 输入参数: pBuffer1:要比较的缓冲区1的指针
  *           pBuffer2:要比较的缓冲区2的指针
  *           BufferLength:缓冲区长度
  * 返 回 值: PASSED:相等
  *           FAILED:不等
  * 说    明: 无
  */
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
  while(BufferLength--)
  {
    if(*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }

    pBuffer1++;
    pBuffer2++;
  }
  return PASSED;
}

2.flash整数存储

main.c

#include "stm32f4xx_hal.h"
#include "usart/bsp_debug_usart.h"
#include "spiflash/bsp_spiflash.h"
#include "led/bsp_led.h"

/* 私有类型定义 --------------------------------------------------------------*/
typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;

/* 私有宏定义 ----------------------------------------------------------------*/
/* 获取缓冲区的长度 */
#define  FLASH_WriteAddress     0x0000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress

/* 私有变量 ------------------------------------------------------------------*/
uint8_t Tx_Buffer[6] = {1,50,100,150,200,250};
uint8_t Rx_Buffer[6] = {0};

uint32_t DeviceID = 0;
uint32_t FlashID = 0;
__IO TestStatus TransferStatus = FAILED;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength);

/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 系统时钟配置
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
  __HAL_RCC_PWR_CLK_ENABLE();                                     //使能PWR时钟

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  //设置调压器输出电压级别1

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                        //打开HSE 
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    //打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            //PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLM = 8;                                 //8分频MHz
  RCC_OscInitStruct.PLL.PLLN = 336;                               //336倍频
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     //2分频,得到168MHz主时钟
  RCC_OscInitStruct.PLL.PLLQ = 7;                                 //USB/SDIO/随机数产生器等的主PLL分频系数
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHz
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

  HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用
  
 	// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                // 配置并启动系统滴答定时器
  /* 系统滴答定时器时钟源 */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* 系统滴答定时器中断优先级配置 */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{
  uint8_t i;  
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* LED灯初始化 */
  LED_GPIO_Init();
  
  /* 初始化串口并配置串口中断优先级 */
  MX_DEBUG_USART_Init();
  /* 调用格式化输出函数打印输出数据 */
  printf("这是一个16M byte串行flash(W25Q128)读写测试实验\n"); 
  
  MX_SPIFlash_Init();
  
  /* Get SPI Flash Device ID */
	DeviceID = SPI_FLASH_ReadDeviceID();
  
  HAL_Delay(100);
  
  /* Get SPI Flash ID */
	FlashID = SPI_FLASH_ReadID();
  
  printf("FlashID is 0x%X,  Manufacturer Device ID is 0x%X\n", FlashID, DeviceID);
	
	/* Check the SPI Flash ID */
	if (FlashID == SPI_FLASH_ID)  /* #define  sFLASH_ID  0XEF4018 */
	{	
		printf("检测到华邦串行flash W25Q128 !\n");
		
		/* 擦除SPI的扇区以写入 */
		SPI_FLASH_SectorErase(FLASH_SectorToErase);	 	 
		
		/* 将发送缓冲区的数据写到flash中 */ 	
    SPI_FLASH_BufferWrite(Tx_Buffer,FLASH_WriteAddress, sizeof(Tx_Buffer));
    printf("写入的数据为:\n");
    for(i=0; i<sizeof(Tx_Buffer);i++ )
      printf("%d ", Tx_Buffer[i]);
    printf("\n");
    
    /* 将刚刚写入的数据读出来放到接收缓冲区中 */
		SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, sizeof(Rx_Buffer));
    printf("读出的数据为:\n");
    for(i=0; i<sizeof(Rx_Buffer);i++ )
      printf("%d ", Rx_Buffer[i]);
    printf("\n");
		
		
    /* 检查写入的数据与读出的数据是否相等 */
		TransferStatus = Buffercmp(Tx_Buffer, Rx_Buffer, sizeof(Tx_Buffer));
		
		if( PASSED == TransferStatus )
		{    
			printf("16M串行flash(W25Q128)数据读写测试成功!\n");
      LED1_ON;
		}
		else
		{        
			printf("16M串行flash(W25Q128)数据读写测试失败!\n");
      LED2_ON;
		}
	}
	else
	{    
		printf("获取不到 W25Q128 ID!\n");
		LED3_ON;
	}
  /* 无限循环 */
  while (1)
  {
  }
}

/**
  * 函数功能: 比较两个缓冲区中的数据是否相等
  * 输入参数: pBuffer1:要比较的缓冲区1的指针
  *           pBuffer2:要比较的缓冲区2的指针
  *           BufferLength:缓冲区长度
  * 返 回 值: PASSED:相等
  *           FAILED:不等
  * 说    明: 无
  */
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
  while(BufferLength--)
  {
    if(*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }

    pBuffer1++;
    pBuffer2++;
  }
  return PASSED;
}

3.flash小数存储

spi.h

#ifndef __BSP_SPIFLASH_H__
#define __BSP_SPIFLASH_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
//#define  SPI_FLASH_ID                       0x3015     //W25X16   2MB
//#define  SPI_FLASH_ID                       0x4015	   //W25Q16   4MB
//#define  SPI_FLASH_ID                       0X4017     //W25Q64   8MB
#define  SPI_FLASH_ID                       0X4018     //W25Q128  16MB YS-F4Pro开发默认使用

#define FLASH_SPIx                                 SPI2
#define FLASH_SPIx_RCC_CLK_ENABLE()                __HAL_RCC_SPI2_CLK_ENABLE()
#define FLASH_SPIx_RCC_CLK_DISABLE()               __HAL_RCC_SPI2_CLK_DISABLE()

#define FLASH_SPI_GPIO_ClK_ENABLE()                {__HAL_RCC_GPIOI_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();} 
#define FLASH_SPI_GPIO_PORT                        GPIOI
#define FLASH_SPI_SCK_PIN                          GPIO_PIN_1

#define FLASH_SPI_MOSI_PIN                         GPIO_PIN_3
    
#define FLASH_SPI_CS_PIN                           GPIO_PIN_0

#define FLASH_SPI_CS_ENABLE()                      HAL_GPIO_WritePin(FLASH_SPI_GPIO_PORT, FLASH_SPI_CS_PIN, GPIO_PIN_RESET)
#define FLASH_SPI_CS_DISABLE()                     HAL_GPIO_WritePin(FLASH_SPI_GPIO_PORT, FLASH_SPI_CS_PIN, GPIO_PIN_SET)

#define FLASH_SPI_MISO_PORT                        GPIOC
#define FLASH_SPI_MISO_PIN                         GPIO_PIN_2


/* 扩展变量 ------------------------------------------------------------------*/
extern SPI_HandleTypeDef hspiflash;

/* 函数声明 ------------------------------------------------------------------*/

void MX_SPIFlash_Init(void);
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
uint32_t SPI_FLASH_ReadID(void);
uint32_t SPI_FLASH_ReadDeviceID(void);
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);

uint8_t SPI_FLASH_ReadByte(void);
uint8_t SPI_FLASH_SendByte(uint8_t byte);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);

#endif  /* __BSP_SPIFLASH_H__ */

spi.c

#include "spiflash/bsp_spiflash.h"
#include "usart/bsp_debug_usart.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256
#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 WIP_Flag                        0x01  /* Write In Progress (WIP) flag */

#define Dummy_Byte                      0xFF

/* 私有变量 ------------------------------------------------------------------*/
SPI_HandleTypeDef hspiflash;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/

/**
  * 函数功能: 串行FLASH初始化
  * 输入参数: huart:串口句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
*/
void MX_SPIFlash_Init(void)
{
  hspiflash.Instance = FLASH_SPIx;
  hspiflash.Init.Mode = SPI_MODE_MASTER;//主模式
  hspiflash.Init.Direction = SPI_DIRECTION_2LINES;//双向
  hspiflash.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据帧长度
  hspiflash.Init.CLKPolarity = SPI_POLARITY_LOW;//时钟极性低
  hspiflash.Init.CLKPhase = SPI_PHASE_1EDGE;//奇数位
  hspiflash.Init.NSS = SPI_NSS_SOFT;//软件
  hspiflash.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;//预分频系数为4
  hspiflash.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行
  hspiflash.Init.TIMode = SPI_TIMODE_DISABLE;
  hspiflash.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//CRC校验失能
  hspiflash.Init.CRCPolynomial = 10;
  HAL_SPI_Init(&hspiflash);
}

/**
  * 函数功能: SPI外设系统级初始化
  * 输入参数: hspi:SPI句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  if(hspi->Instance==FLASH_SPIx)
  {
    /* SPI外设时钟使能 */
    FLASH_SPIx_RCC_CLK_ENABLE();
    /* GPIO外设时钟使能 */
    FLASH_SPI_GPIO_ClK_ENABLE();
    
    /**SPI2 GPIO Configuration    
    PI0     ------> SPI2_NSS
    PI1     ------> SPI2_SCK
    PC2     ------> SPI2_MISO
    PI3     ------> SPI2_MOSI 
    */
    GPIO_InitStruct.Pin = FLASH_SPI_SCK_PIN|FLASH_SPI_MOSI_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(FLASH_SPI_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = FLASH_SPI_MISO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStruct);
    
    HAL_GPIO_WritePin(FLASH_SPI_GPIO_PORT, FLASH_SPI_CS_PIN, GPIO_PIN_SET);
    GPIO_InitStruct.Pin = FLASH_SPI_CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(FLASH_SPI_GPIO_PORT, &GPIO_InitStruct);
  }
}

/**
  * 函数功能: SPI外设系统级反初始化
  * 输入参数: hspi:SPI句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* hspi)
{

  if(hspi->Instance==FLASH_SPIx)
  {
    /* SPI外设时钟禁用 */
    FLASH_SPIx_RCC_CLK_DISABLE();
  
    /**SPI2 GPIO Configuration    
    PI0     ------> SPI2_NSS
    PI1     ------> SPI2_SCK
    PC2     ------> SPI2_MISO
    PI3     ------> SPI2_MOSI 
    */
    HAL_GPIO_DeInit(FLASH_SPI_GPIO_PORT, FLASH_SPI_SCK_PIN|FLASH_SPI_MOSI_PIN|FLASH_SPI_CS_PIN);
    HAL_GPIO_DeInit(FLASH_SPI_MISO_PORT, FLASH_SPI_MISO_PIN);
  }
} 


/**
  * 函数功能: 擦除扇区
  * 输入参数: SectorAddr:待擦除扇区地址,要求为4096倍数
  * 返 回 值: 无
  * 说    明:串行Flash最小擦除块大小为4KB(4096字节),即一个扇区大小,要求输入参数
  *           为4096倍数。在往串行Flash芯片写入数据之前要求先擦除空间。
  */
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();
  SPI_FLASH_WaitForWriteEnd();
  /* 擦除扇区 */
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 发送扇区擦除指令*/
  SPI_FLASH_SendByte(W25X_SectorErase);
  /*发送擦除扇区地址的高位*/
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  /* 发送擦除扇区地址的中位 */
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  /* 发送擦除扇区地址的低位 */
  SPI_FLASH_SendByte(SectorAddr & 0xFF);
  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();
  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 擦除整片
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:擦除串行Flash整片空间
  */
void SPI_FLASH_BulkErase(void)
{
 /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

  /* 整片擦除 Erase */
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 发送整片擦除指令*/
  SPI_FLASH_SendByte(W25X_ChipErase);
  /* 禁用串行FLASH: CS高电平 */
  FLASH_SPI_CS_DISABLE();

  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 往串行FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
  * 输入参数: pBuffer:待写入数据的指针
  *           WriteAddr:写入地址
  *           NumByteToWrite:写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize
  * 返 回 值: 无
  * 说    明:串行Flash每页大小为256个字节
  */
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

   /* 寻找串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 写送写指令*/
  SPI_FLASH_SendByte(W25X_PageProgram);
  /*发送写地址的高位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  /*发送写地址的中位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  /*发送写地址的低位*/
  SPI_FLASH_SendByte(WriteAddr & 0xFF);

  if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
  {
     NumByteToWrite = SPI_FLASH_PerWritePageSize;
     //printf("Err: SPI_FLASH_PageWrite too large!\n");
  }

  /* 写入数据*/
  while (NumByteToWrite--)
  {
     /* 发送当前要写入的字节数据 */
    SPI_FLASH_SendByte(*pBuffer);
     /* 指向下一字节数据 */
    pBuffer++;
  }

  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();

  /* 等待写入完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 往串行FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * 输入参数: pBuffer:待写入数据的指针
  *           WriteAddr:写入地址
  *           NumByteToWrite:写入数据长度
  * 返 回 值: 无
  * 说    明:该函数可以设置任意写入数据长度
  */
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % SPI_FLASH_PageSize;
  count = SPI_FLASH_PageSize - Addr;
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

  if (Addr == 0) /* 若地址与 SPI_FLASH_PageSize 对齐  */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }

      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else /* 若地址与 SPI_FLASH_PageSize 不对齐 */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
      {
        temp = NumOfSingle - count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;

      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }

      if (NumOfSingle != 0)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

/**
  * 函数功能: 从串行Flash读取数据
  * 输入参数: pBuffer:存放读取到数据的指针
  *           ReadAddr:读取数据目标地址
  *           NumByteToRead:读取数据长度
  * 返 回 值: 无
  * 说    明:该函数可以设置任意读取数据长度
  */
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送 读 指令 */
  SPI_FLASH_SendByte(W25X_ReadData);

  /* 发送 读 地址高位 */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* 发送 读 地址中位 */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* 发送 读 地址低位 */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);

  while (NumByteToRead--) /* 读取数据 */
  {
     /* 读取一个字节*/
    *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
    /* 指向下一个字节缓冲区 */
    pBuffer++;
  }

  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();
}

/**
  * 函数功能: 读取串行Flash型号的ID
  * 输入参数: 无
  * 返 回 值: uint32_t:串行Flash的型号ID
  * 说    明:  FLASH_ID      IC型号      存储空间大小         
                0x3015      W25X16        2M byte
                0x4015	    W25Q16        4M byte
                0X4017      W25Q64        8M byte
                0X4018      W25Q128       16M byte  (YS-F1Pro开发板默认配置)
  */
uint32_t SPI_FLASH_ReadID(void)
{
  uint32_t Temp = 0, Temp1 = 0, Temp2 = 0;

  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:读取芯片型号ID */
  SPI_FLASH_SendByte(W25X_JedecDeviceID);

  /* 从串行Flash读取一个字节数据 */
  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 从串行Flash读取一个字节数据 */
  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 从串行Flash读取一个字节数据 */
  Temp2 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();
  
  Temp = Temp1 << 8 | Temp2;
  return Temp;
}

/**
  * 函数功能: 读取串行Flash设备ID
  * 输入参数: 无
  * 返 回 值: uint32_t:串行Flash的设备ID
  * 说    明:
  */
uint32_t SPI_FLASH_ReadDeviceID(void)
{
  uint32_t Temp = 0;

  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:读取芯片设备ID * */
  SPI_FLASH_SendByte(W25X_DeviceID);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  
  /* 从串行Flash读取一个字节数据 */
  Temp = SPI_FLASH_SendByte(Dummy_Byte);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();

  return Temp;
}

/**
  * 函数功能: 启动连续读取数据串
  * 输入参数: ReadAddr:读取地址
  * 返 回 值: 无
  * 说    明:Initiates a read data byte (READ) sequence from the Flash.
  *           This is done by driving the /CS line low to select the device,
  *           then the READ instruction is transmitted followed by 3 bytes
  *           address. This function exit and keep the /CS line low, so the
  *           Flash still being selected. With this technique the whole
  *           content of the Flash is read with a single READ instruction.
  */
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr)
{
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Read from Memory " instruction */
  SPI_FLASH_SendByte(W25X_ReadData);

  /* Send the 24-bit address of the address to read from -----------------------*/
  /* Send ReadAddr high nibble address byte */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* Send ReadAddr medium nibble address byte */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* Send ReadAddr low nibble address byte */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
}

/**
  * 函数功能: 从串行Flash读取一个字节数据
  * 输入参数: 无
  * 返 回 值: uint8_t:读取到的数据
  * 说    明:This function must be used only if the Start_Read_Sequence
  *           function has been previously called.
  */
uint8_t SPI_FLASH_ReadByte(void)
{
  uint8_t d_read,d_send=Dummy_Byte;
  if(HAL_SPI_TransmitReceive(&hspiflash,&d_send,&d_read,1,0xFFFFFF)!=HAL_OK)
    d_read=Dummy_Byte;
  
  return d_read;    
}

/**
  * 函数功能: 往串行Flash读取写入一个字节数据并接收一个字节数据
  * 输入参数: byte:待发送数据
  * 返 回 值: uint8_t:接收到的数据
  * 说    明:无
  */
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
  uint8_t d_read,d_send=byte;
  if(HAL_SPI_TransmitReceive(&hspiflash,&d_send,&d_read,1,0xFFFFFF)!=HAL_OK)
    d_read=Dummy_Byte;
  
  return d_read; 
}

/**
  * 函数功能: 使能串行Flash写操作
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_FLASH_WriteEnable(void)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:写使能 */
  SPI_FLASH_SendByte(W25X_WriteEnable);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();
}

/**
  * 函数功能: 等待数据写入完成
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:Polls the status of the Write In Progress (WIP) flag in the
  *           FLASH's status  register  and  loop  until write  opertaion
  *           has completed.
  */
void SPI_FLASH_WaitForWriteEnd(void)
{
  uint8_t FLASH_Status = 0;

  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Read Status Register" instruction */
  SPI_FLASH_SendByte(W25X_ReadStatusReg);

  /* Loop as long as the memory is busy with a write cycle */
  do
  {
    /* Send a dummy byte to generate the clock needed by the FLASH
    and put the value of the status register in FLASH_Status variable */
    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);	 
  }
  while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress */

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE();
}


/**
  * 函数功能: 进入掉电模式
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_Flash_PowerDown(void)   
{ 
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Power Down" instruction */
  SPI_FLASH_SendByte(W25X_PowerDown);

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE();
}   

/**
  * 函数功能: 唤醒串行Flash
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_Flash_WAKEUP(void)   
{
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Power Down" instruction */
  SPI_FLASH_SendByte(W25X_ReleasePowerDown);

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE(); 
}   

main.c

#include "stm32f4xx_hal.h"
#include "usart/bsp_debug_usart.h"
#include "spiflash/bsp_spiflash.h"

/* 私有类型定义 --------------------------------------------------------------*/
typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;

/* 私有宏定义 ----------------------------------------------------------------*/
/* 私有变量 ------------------------------------------------------------------*/
/* 发送缓冲区初始化 */
long double Tx_Buffer[7] = {0};
long double Rx_Buffer[7] = {0};
uint8_t cal_flag = 0;
uint8_t cal_f = 0;

uint32_t DeviceID = 0;
uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 系统时钟配置
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
  __HAL_RCC_PWR_CLK_ENABLE();                                     //使能PWR时钟

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  //设置调压器输出电压级别1

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                        //打开HSE 
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    //打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            //PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLM = 8;                                 //8分频MHz
  RCC_OscInitStruct.PLL.PLLN = 336;                               //336倍频
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     //2分频,得到168MHz主时钟
  RCC_OscInitStruct.PLL.PLLQ = 7;                                 //USB/SDIO/随机数产生器等的主PLL分频系数
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHz
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

  HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用
  
 	// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                // 配置并启动系统滴答定时器
  /* 系统滴答定时器时钟源 */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* 系统滴答定时器中断优先级配置 */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{
  uint8_t i;  
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  
  /* 初始化串口并配置串口中断优先级 */
  MX_DEBUG_USART_Init();
  /* 调用格式化输出函数打印输出数据 */
  printf("这是一个16M byte串行flash(W25Q128)读写测试实验\n"); 
  
  MX_SPIFlash_Init();
  
  /* Get SPI Flash Device ID */
	DeviceID = SPI_FLASH_ReadDeviceID();
  
  HAL_Delay(100);
  
  /* Get SPI Flash ID */
	FlashID = SPI_FLASH_ReadID();
  
  printf("FlashID is 0x%X,  Manufacturer Device ID is 0x%X\n", FlashID, DeviceID);
	
	/* Check the SPI Flash ID */
	if (FlashID == SPI_FLASH_ID)  /* #define  sFLASH_ID  0XEF4018 */
	{	
		printf("检测到华邦串行flash W25Q128 !\n");
		
		SPI_FLASH_BufferRead(&cal_flag, 0, 1);
    if( cal_flag == 0x55)
    {      
        SPI_FLASH_BufferRead((void*)Rx_Buffer, 1, sizeof(Rx_Buffer));
        for(i=0;i<7;i++ )
          printf("rx = %LF \n",Rx_Buffer[i]);
    }    
    else
    {
      cal_flag = 0x55;
      SPI_FLASH_SectorErase(0);
      SPI_FLASH_BufferWrite(&cal_flag, 0, 1); 
      
      for( i=0; i<7; i++ )
          Tx_Buffer[i] = i +0.1;
      
      SPI_FLASH_BufferWrite((void*)Tx_Buffer, 1, sizeof(Tx_Buffer));
      
      for(i=0; i<7;i++ )
        printf("tx = %LF \n",Tx_Buffer[i]);
    } 
	}
	else
	{    
		printf("获取不到 W25Q128 ID!\n");
	}
  /* 无限循环 */
  while (1)
  {
  }
}

4.SPI读写磁编码器

spi.h

#ifndef __BSP_SPI_H__
#define __BSP_SPI_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
#define SPIx                            SPI1
#define SPIx_RCC_CLK_ENABLE()           __HAL_RCC_SPI1_CLK_ENABLE()
#define SPIx_RCC_CLK_DISABLE()          __HAL_RCC_SPI1_CLK_DISABLE()

#define SPIx_SCK_ClK_ENABLE()            __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPIx_SCK_PORT                    GPIOA
#define SPIx_SCK_PIN                     GPIO_PIN_5

#define SPIx_MOSI_ClK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MOSI_PORT                   GPIOB
#define SPIx_MOSI_PIN                    GPIO_PIN_5

#define SPIx_MISO_ClK_ENABLE()           __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MISO_PORT                   GPIOB
#define SPIx_MISO_PIN                    GPIO_PIN_4

#define SPIx_CS_CLK_ENABLE()             __HAL_RCC_GPIOC_CLK_ENABLE()    
#define SPIx_CS_PORT                     GPIOC
#define SPIx_CS_PIN                      GPIO_PIN_13
#define SPIx_CS_ENABLE()                 HAL_GPIO_WritePin(SPIx_CS_PORT, SPIx_CS_PIN, GPIO_PIN_RESET)
#define SPIx_CS_DISABLE()                HAL_GPIO_WritePin(SPIx_CS_PORT, SPIx_CS_PIN, GPIO_PIN_SET)
               
#define SPI_DATA_SIZE                    SPI_DATASIZE_16BIT
/* 扩展变量 ------------------------------------------------------------------*/
extern SPI_HandleTypeDef hspix;

/* 函数声明 ------------------------------------------------------------------*/

void SPIx_Init(void);

#if (SPI_DATA_SIZE)==(SPI_DATASIZE_16BIT)                    
  uint16_t SPIx_Write_Buf(uint16_t reg, uint16_t *pBuf, uint16_t uint8_ts);//写数据区
  uint16_t SPIx_Read_Buf(uint16_t reg, uint16_t *pBuf, uint16_t uint8_ts);	//读数据区		  
  uint16_t SPIx_Read_Reg(uint16_t reg);					//读寄存器
  uint16_t SPIx_Write_Reg(uint16_t reg, uint16_t value);		//写寄存器
  uint16_t SPIx_ReadWriteByte(SPI_HandleTypeDef* hspi,uint16_t Byte);
#else
  uint8_t SPIx_ReadWriteByte(SPI_HandleTypeDef* hspi,uint8_t Byte);
  uint8_t SPIx_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t uint8_ts);//写数据区
  uint8_t SPIx_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t uint8_ts);	//读数据区		  
  uint8_t SPIx_Read_Reg(uint8_t reg);					//读寄存器
  uint8_t SPIx_Write_Reg(uint8_t reg, uint8_t value);		//写寄存器
#endif


#endif  /* __BSP_SPI_H__ */

spi.c

#include "SPI/bsp_SPI.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/

/* 私有变量 ------------------------------------------------------------------*/
SPI_HandleTypeDef hspix;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/

/**
  * 函数功能: 串行FLASH初始化
  * 输入参数: huart:串口句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
*/
void SPIx_Init(void)
{
  SPIx_RCC_CLK_ENABLE();

  hspix.Instance = SPIx;
  hspix.Init.Mode = SPI_MODE_MASTER;
  hspix.Init.Direction = SPI_DIRECTION_2LINES;
  hspix.Init.DataSize = SPI_DATA_SIZE;
  hspix.Init.CLKPolarity = SPI_POLARITY_LOW; // 时钟空闲电平极性
  hspix.Init.CLKPhase = SPI_PHASE_2EDGE;     // 时钟相位, 时钟第一个边沿采样
  hspix.Init.NSS = SPI_NSS_SOFT;
  hspix.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
  hspix.Init.FirstBit = SPI_FIRSTBIT_MSB;    // MSB 
  hspix.Init.TIMode = SPI_TIMODE_DISABLE;
  hspix.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspix.Init.CRCPolynomial = 7;
  HAL_SPI_Init(&hspix);
  __HAL_SPI_ENABLE(&hspix);
}

/**
  * 函数功能: SPI外设系统级初始化
  * 输入参数: hspi:SPI句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  if(hspi == &hspix)
  {
    SPIx_MOSI_ClK_ENABLE();
    SPIx_MISO_ClK_ENABLE();
    SPIx_SCK_ClK_ENABLE();
    SPIx_CS_CLK_ENABLE();  
    /**SPI1 GPIO Configuration    
    PA5     ------> SPI1_SCK
    PB4     ------> SPI1_MISO
    PB5     ------> SPI1_MOSI 
    */
    HAL_GPIO_WritePin( SPIx_CS_PORT,  SPIx_CS_PIN, GPIO_PIN_SET);
    GPIO_InitStruct.Pin =  SPIx_CS_PIN;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF0_RTC_50Hz;
    HAL_GPIO_Init( SPIx_CS_PORT, &GPIO_InitStruct);
    
    GPIO_InitStruct.Pin = SPIx_SCK_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init( SPIx_SCK_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin =  SPIx_MISO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init( SPIx_MISO_PORT, &GPIO_InitStruct);
    
    GPIO_InitStruct.Pin = SPIx_MOSI_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init( SPIx_MOSI_PORT, &GPIO_InitStruct);
  }
}

#if (SPI_DATA_SIZE)==(SPI_DATASIZE_16BIT)  
/**
  * 函数功能: 往SPI设备读取写入一个字节数据并接收一个字节数据
  * 输入参数: byte:待发送数据
  * 返 回 值: uint8_t:接收到的数据
  * 说    明:无
  */
uint16_t SPIx_ReadWriteByte(SPI_HandleTypeDef* hspi,uint16_t Byte)
{
  uint16_t d_read = 0xFFFF;
  uint16_t d_send = Byte;
  SPIx_CS_ENABLE();                 //使能SPIx传输
  if(HAL_SPI_TransmitReceive(hspi,(uint8_t*)&d_send,(uint8_t*)&d_read,1,0x0F)!=HAL_OK)
  {
    d_read = 0xFFFF;
  }
  SPIx_CS_DISABLE();                 //禁止SPIx传输	   
  return d_read; 
} 
/**
  * 函数功能: SPI写寄存器
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:reg:指定寄存器地址,value:写入寄存器的数值
  *           
  */ 
uint16_t SPIx_Write_Reg(uint16_t reg,uint16_t value)
{
	uint16_t status;	
  status = SPIx_ReadWriteByte(&hspix,reg);//发送寄存器号 
  SPIx_ReadWriteByte(&hspix,value);      //写入寄存器的值
  return(status);       			//返回状态值
}


/**
  * 函数功能: 读取SPI寄存器值
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:reg:要读的寄存器
  *           
  */ 
uint16_t  SPIx_Read_Reg(uint16_t reg)
{
	uint16_t reg_val;	    
  SPIx_ReadWriteByte(&hspix,reg);   //发送寄存器号
  reg_val=SPIx_ReadWriteByte(&hspix,0XFFFF);//读取寄存器内容
  return(reg_val);           //返回状态值
}	

/**
  * 函数功能: 在指定位置读出指定长度的数据
  * 输入参数: 无
  * 返 回 值: 此次读到的状态寄存器值 
  * 说    明:无
  *           
  */ 
uint16_t  SPIx_Read_Buf(uint16_t reg,uint16_t *pBuf,uint16_t len)
{
	uint16_t status,uint8_t_ctr;	   
  
  status=SPIx_ReadWriteByte(&hspix,reg);//发送寄存器值(位置),并读取状态值   	   
 	for(uint8_t_ctr=0;uint8_t_ctr<len;uint8_t_ctr++)
  {
    pBuf[uint8_t_ctr]=SPIx_ReadWriteByte(&hspix,0XFFFF);//读出数据
  }
  return status;        //返回读到的状态值
}

/**
  * 函数功能: 在指定位置写指定长度的数据
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:reg:寄存器(位置)  *pBuf:数据指针  len:数据长度
  *           
  */ 
uint16_t SPIx_Write_Buf(uint16_t reg, uint16_t *pBuf, uint16_t len)
{
	uint16_t status,uint8_t_ctr;	    
  status = SPIx_ReadWriteByte(&hspix,reg);//发送寄存器值(位置),并读取状态值
  for(uint8_t_ctr=0; uint8_t_ctr<len; uint8_t_ctr++)
  {
    SPIx_ReadWriteByte(&hspix,*pBuf++); //写入数据	 
  }
  return status;          //返回读到的状态值
}				 
#else
/**
  * 函数功能: 往SPI设备读取写入一个字节数据并接收一个字节数据
  * 输入参数: byte:待发送数据
  * 返 回 值: uint8_t:接收到的数据
  * 说    明:无
  */
uint8_t SPIx_ReadWriteByte(SPI_HandleTypeDef* hspi,uint8_t Byte)
{
  uint8_t d_read = 0xFF;
  uint8_t d_send = Byte;
  if(HAL_SPI_TransmitReceive(hspi,(uint8_t*)&d_send,(uint8_t*)&d_read,1,0xFF)!=HAL_OK)
  {
    d_read = 0xFF;
  }
  return d_read; 
} 

/**
  * 函数功能: SPI写寄存器
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:reg:指定寄存器地址,value:写入寄存器的数值
  *           
  */ 
uint8_t SPIx_Write_Reg(uint8_t reg,uint8_t value)
{
	uint8_t status;	
  SPIx_CS_ENABLE();                 //使能SPIx传输
  status = SPIx_ReadWriteByte(&hspix,reg);//发送寄存器号 
  SPIx_ReadWriteByte(&hspix,value);      //写入寄存器的值
  SPIx_CS_DISABLE();                 //禁止SPIx传输	   
  return(status);       			//返回状态值
}

/**
  * 函数功能: 读取SPI寄存器值
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:reg:要读的寄存器
  *           
  */ 
uint8_t  SPIx_Read_Reg(uint8_t reg)
{
	uint8_t reg_val;	    
 	SPIx_CS_ENABLE();          //使能SPI传输		
  SPIx_ReadWriteByte(&hspix,reg);   //发送寄存器号
  reg_val=SPIx_ReadWriteByte(&hspix,0XFF);//读取寄存器内容
  SPIx_CS_DISABLE();          //禁止SPIx传输		    
  return(reg_val);           //返回状态值
}	

/**
  * 函数功能: 在指定位置读出指定长度的数据
  * 输入参数: 无
  * 返 回 值: 此次读到的状态寄存器值 
  * 说    明:无
  *           
  */ 
uint8_t  SPIx_Read_Buf(uint8_t reg,uint8_t *pBuf,uint8_t len)
{
	uint8_t status,uint8_t_ctr;	   
  
  SPIx_CS_ENABLE();           //使能SPIx传输
  status=SPIx_ReadWriteByte(&hspix,reg);//发送寄存器值(位置),并读取状态值   	   
 	for(uint8_t_ctr=0;uint8_t_ctr<len;uint8_t_ctr++)
  {
    pBuf[uint8_t_ctr]=SPIx_ReadWriteByte(&hspix,0XFF);//读出数据
  }
  SPIx_CS_DISABLE();       //关闭SPIx传输
  return status;        //返回读到的状态值
}

/**
  * 函数功能: 在指定位置写指定长度的数据
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:reg:寄存器(位置)  *pBuf:数据指针  len:数据长度
  *           
  */ 
uint8_t SPIx_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
	uint8_t status,uint8_t_ctr;	    
 	SPIx_CS_ENABLE();          //使能SPIx传输
  status = SPIx_ReadWriteByte(&hspix,reg);//发送寄存器值(位置),并读取状态值
  for(uint8_t_ctr=0; uint8_t_ctr<len; uint8_t_ctr++)
  {
    SPIx_ReadWriteByte(&hspix,*pBuf++); //写入数据	 
  }
  SPIx_CS_DISABLE();       //关闭SPIx传输
  return status;          //返回读到的状态值
}				   
#endif

磁编码器.h

#ifndef __BSP_AS5048A_H__
#define __BSP_AS5048A_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* 类型定义 ------------------------------------------------------------------*/
typedef struct 
{
  uint32_t Uint32;
  float Float;
}Float_ByteTypeDef;

typedef struct 
{
  uint16_t RegAutoGainCtrl;// 输出寄存器->Automatic Gain Control 
  uint16_t RegMagnitude;   // 输出寄存器->Magnirude 
  uint16_t RegAngle;       // 输出寄存器->Angle (Angle value in Register (0x0...0x3FFF))
  Float_ByteTypeDef AngleValue;// 角度值(degree) (0..359°)/(0..359.9°)
}AS5048_ReadDataTypeDef ;

/* 宏定义 --------------------------------------------------------------------*/
#define SPI_CMD_READ          0x4000  // 读标志位bit 14
#define SPI_CMD_WRITE         0x0000  // 写标志位bit 14
#define SPI_REG_AGC           0X3FFD  // ARG 寄存器地址
#define SPI_REG_MAG           0X3FFE  // MAGNITUDE寄存器地址
#define SPI_REG_DATA          0X3FFF  // 数据寄存器地址

#define SPI_REG_CLRERR        0x0001  // 错误标志寄存器地址
#define SPI_REG_ZEROPOS_HI    0x0016  // 零点设定高寄存器地址
#define SPI_REG_ZEROPOS_LO    0x0017  // 零点设定低寄存器地址

/* 扩展变量 ------------------------------------------------------------------*/
extern AS5048_ReadDataTypeDef ReadoutREG; // 输出寄存器值
/* 函数声明 ------------------------------------------------------------------*/
void AS5048AReadData(void);
uint16_t AS5048A_GetAverageAngle(void);
#endif

磁编码器.c

/* 包含头文件 ----------------------------------------------------------------*/
#include "AS5048A/bsp_AS5048A.h"
#include "SPI/bsp_SPI.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
#define AVERAGE       4 // 取4次数据做平均值,Sensor output noise 2.73LSB@14bit
#define AVERAGE_POWER 2 // 2^2 = AVERAGE
/* 私有变量 ------------------------------------------------------------------*/
AS5048_ReadDataTypeDef ReadoutREG; // 输出寄存器
/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
static uint8_t CalcEvenParity(uint16_t value);
/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 计算偶校验位函数
  * 输入参数: value | uint6_t 待校验的数据
  * 返 回 值: 校验位结果
  * 说    明: 16位无符号整数的奇偶校验,如果是偶数个1,则返回0. 
  */
static uint8_t CalcEvenParity(uint16_t value)
{
  uint16_t cnt = 0;
  while(value)
  {
    cnt += value & 0x1;
    value >>= 1;
  }
  return cnt & 0x1;
}

/**
  * 函数功能: SPI读取数据
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:读取输出寄存器的数据,Readout Registers
  *           
  */ 
void AS5048AReadData()
{
  uint16_t dat; // 16-bit 数据,用于SPI通信
  uint16_t magreg,agcreg;
  uint16_t value;
  /* 发送读AGC指令,丢弃接收到的数据:数据来自于上一个指令(未知)*/
  dat = SPI_CMD_READ | SPI_REG_AGC;
  dat |= CalcEvenParity(dat) << 15;
  dat = SPIx_ReadWriteByte(&hspix,dat);

  /* 发送读MAG指令. 接收到的数据是AGC值:数据来自于上一个指令 (SPI_REG_AGC)*/
  dat = SPI_CMD_READ | SPI_REG_MAG;
  dat |= CalcEvenParity(dat) << 15;
  dat = SPIx_ReadWriteByte(&hspix,dat);
  agcreg = dat;
  
  /* 发送读Angle指令. 接收到的数据是MAG值:数据来自于上一个指令 (SPI_REG_MAG)*/
  dat = SPI_CMD_READ | SPI_REG_DATA;
  dat |= CalcEvenParity(dat) << 15;
  dat = SPIx_ReadWriteByte(&hspix,dat);
  magreg = dat;
  
  /* 发送读NOP指令. 接收到的数据是Angle值:数据来自于上一个指令 (SPI_REG_DATA)*/
  dat = 0x0000; // NOP command.芯片响应命令是0x0000
  dat = SPIx_ReadWriteByte(&hspix,dat);
  
  /* error flag set - need to reset it */
  if (dat & 0x4000 )
  {
    dat = SPI_CMD_READ | SPI_REG_CLRERR;
    dat |= CalcEvenParity(dat)<<15;
    SPIx_ReadWriteByte(&hspix,dat);
  }
  else
  {
    value = dat & (16384 - 31 - 1); // Angle value (0.. 16384 steps)
    ReadoutREG.RegAngle = value;
    ReadoutREG.RegMagnitude = magreg & (16384 - 31 - 1);
    ReadoutREG.RegAutoGainCtrl = agcreg & 0XFF;// AGC value (0..255)
    
    ReadoutREG.AngleValue.Float = (float)(value * 360) / 16384.0f;// Angle value in degree (0..359.9°)
    ReadoutREG.AngleValue.Uint32 = (uint32_t)ReadoutREG.AngleValue.Float;
//    HAL_Delay(5);
  }
}

/**
  * 函数功能: 读取传感器的角度值
  * 输入参数: 无
  * 返 回 值: Angle | 角度值
  * 说    明: 读取编码器的角度值
  */
uint16_t Sensor_Read()
{
  uint16_t dat = 0;
  uint32_t Angle = 0 ;
  uint32_t value = 0;
  /* 发送读Angle指令. 接收到的数据是MAG值:数据来自于上一个指令 (SPI_REG_MAG)*/
  dat = SPI_CMD_READ | SPI_REG_DATA;
  dat |= CalcEvenParity(dat) << 15;
  dat = SPIx_ReadWriteByte(&hspix,dat);
  
  if (dat & 0x4000 )
  {
    dat = SPI_CMD_READ | SPI_REG_CLRERR;
    dat |= CalcEvenParity(dat)<<15;
    SPIx_ReadWriteByte(&hspix,dat);
  }
  else
  {
    value = dat & (16384 - 31 - 1); // Angle value (0.. 16384 steps)
    Angle = (value * 360) / 16384;// Angle value in degree (0..359.9°)
  }
  return Angle;
}

/**
  * 函数功能: 简易的求平均算法
  * 输入参数: 无
  * 返 回 值: Position | 采样n次之后的均值
  * 说    明: 简单的平均算法来减少噪音的输出.
  */
uint16_t AS5048A_GetAverageAngle()
{
  uint16_t count = AVERAGE;
  uint16_t Position = 0;
  uint16_t data ;
  while(count--)
  {
    data = Sensor_Read(); //reading,computing sensor output
    Position += data;
  }
  Position = Position >> AVERAGE_POWER; // 2^2 = AVERAGE;
  return Position;
}

main.c

#include "stm32f4xx_hal.h"
#include "usart/bsp_debug_usart.h"
#include "led/bsp_led.h"
#include "SPI/bsp_SPI.h"
#include "AS5048A/bsp_AS5048A.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
/* 私有变量 ------------------------------------------------------------------*/
__IO uint16_t AngleValue;
/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/

/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 系统时钟配置
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
 
  __HAL_RCC_PWR_CLK_ENABLE();                                     //使能PWR时钟

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  //设置调压器输出电压级别1

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                        //打开HSE 
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    //打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            //PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLM = 8;                                 //8分频MHz
  RCC_OscInitStruct.PLL.PLLN = 336;                               //336倍频
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     //2分频,得到168MHz主时钟
  RCC_OscInitStruct.PLL.PLLQ = 7;                                 //USB/SDIO/随机数产生器等的主PLL分频系数
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHz
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

  HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用
  
 	// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                // 配置并启动系统滴答定时器
  /* 系统滴答定时器时钟源 */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* 系统滴答定时器中断优先级配置 */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{ 
  
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* SPI初始化 */
  SPIx_Init();
  /* 初始化串口并配置串口中断优先级 */
  MX_DEBUG_USART_Init();
  HAL_Delay(10); // 传感器启动时间是10ms
  /* 无限循环 */
  while (1)
  { 
    HAL_Delay(200);
    AngleValue = AS5048A_GetAverageAngle();
    printf("Angle = %d°\n",AngleValue);
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值