STM32F103,SPI------FLASH

一、SPI简介

        SPI是串行外设接口(Serial Peripheral Interface)的缩写,是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。是一种高速的全双工同步的通信总线,并且在芯片的管教上只占用4根线,节约了芯片的管脚,同时位PCB的布局上节省空间,提供方便,正是这种简单易用的特性,越来越多的芯片集成了这种通信协议,STM32也有SPI接口。

二、SPI应用

        1、4线SPI:

                (1)SCK/SCLK:同步时钟线(由主机产生发送给从机

                (2)CS:片选线(低电平有效)

                (3)MISO:主机输入,从机输出(数据由从机发出)

                (4)MOSI:主机输出,从机输入(数据由主机发出)

        注:时钟信号是一个振荡信号,它告诉接收端在确切的时机对数据线上的信号进行采样

                SPI是一个同步的数据总线,也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的同步

        2、整体传输过程:

                (1)主机先将CS片选线拉低,这样保证开始数据接收

                (2)当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就能得到了一位数据(1bit)。

                (3)主机发送到从机时:主机产生相应的时钟信号,然后数据一位位地从MOSI信号线上进行发送到从机

                (4)主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续产生预定数量的时钟信号,并且从机会将数据通过MISO信号线发送给主机

         

注:SPI是全双工(具有单独的发送和接收线路),因此可以在同一时间发送和接收数据。

        另外SPI的接收硬件可以是一个简单的移位寄存器。 

        3、SPI数据链路层

        

         4、时钟相位和时钟极性

                时钟极性(CPOL): 0   (SCK信号线在空闲时为低电平)规定开始时钟为低电平  

                                                1(SCK信号线在空闲时为 高电平)规定开始时钟为高电平

                时钟相位(CPHA):用来决定何时进行信号采样,0在第一个跳变沿,1在第二个跳变沿,至于是上升沿还是下降沿则由CPOL相位极性来表示

                如果CPHA=0,CPOL=1,则在SCK时钟的第一个边沿为下降沿时进行数据采样。

                如果CPHA=0,CPOL=0,则在SCK时钟的第一个边沿为上升沿时进行数据采样。

                 如果CPHA=1,CPOL=1,则在SCK时钟的第二个边沿为下降沿时进行数据采样。

                如果CPHA=1,CPOL=0,则在SCK时钟的第二个边沿为上升沿时进行数据采样。

                

         5、SPI主要特点有:

                可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

STM32的SPI功能很强大,SPI时钟最多可以到18Mhz,支持DMA,可以配置为SPI协议或者I2S协议

        6、工作框图

        

STM32 SPI NSS大揭秘_andylauren的博客-CSDN博客

SPI主设备(MSTR=1

硬件模式:SSM=0 

输入模式:SSOE=0

在外部NSS引脚为高电平,即内部NSS引脚也为高电平时,才能进行数据传输。 如果要使能从设备,还需要一个GPIO引脚。

允许多主机模式

输出模式:SSOE=1

外部NSS引脚会输出低电平,使能从设备,进行数据传输。 不需要额外的GPIO引脚就能控制从设备。            

软件模式:SSM=1

SSI=1,将内部NSS引脚设置为高电平。这样随时可以传输数据。当然多数情况还需要一个GPIO引脚输出低电平,来使能从设备,让从设备可以接收数据。

SPI从设备(MSTR=0

软件(SSM=1

并SSI=0.让内部NSS引脚为低电平,此时可以传送数据。

硬件(SSM=0

当外部NSS为低电平时,内部NSS也为低电平,此时可以传送数据

        7、FLASH-------(W25Q64模块)

                (1)W25Q64介绍:

                        W25Q64 (64M-bit),W25Q16(16M-bit)和 W25Q32(32M-bit)是为系统提供一个最小的空间、引脚 和功耗的存储器解决方案的串行 Flash 存储器。25Q 系列比普通的串行 Flash 存储器更灵活,性能 更优越。基于双倍/四倍的 SPI,它们能够可以立即完成提供数据给 RAM,包括存储声音、文本和数 据。芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于 5mA,掉电时低于 1uA。

                      W25Q64/16/32 由每页 256 字节组成。每页的 256 字节用一次页编程指令即可完成。每次可以擦 除 16 页(1 个扇区)、128 页(32KB 块)、256 页(64KB 块)和全片擦除。

                存储空间:8M-----------128块/2048扇区

                                1块:16扇区        4096*16字节    即64kb     1kb=1024字节(b)

                                 1扇区:16页      4096字节

                                1页:256字节

                                      0x FF        F        F        FF 

                                           块         扇区  页         字节

                                        例如:0x05 6 3 102   (从而可以确定存储位置:第五块第六扇区第三页)

                      (2)

                        W25Q64/16/32 支持标准串行外围接口(SPI),和高速的双倍/四倍输出,双倍/四倍用的引脚: 串行时钟、片选端、串行数据 I/O0(DI)、I/O1(DO)、I/O2(WP)和 I/O3(HOLD)。SPI 最高支持 80MHz, 当用快读双倍/四倍指令时,相当于双倍输出时最高速率 160MHz,四倍输出时最高速率 320MHz。这 个传输速率比得上 8 位和 16 位的并行 Flash 存储器。 

                               CS:CS为片选管脚,低电平有效。上电之后,在执行一条新命令之前,必须时CS引脚先有个低电平。

                                DO(MISO):DO为串行数据输出引脚,在CLK管脚的下降沿输出数据

                                WP:WP为写保护引脚,有效电平为低电平。高电平可读可写,低电平仅可读。

                                DI(MOSI):DI为串行数据输入引脚,数据、地址和命令从DI引脚输入到芯片内部,在CLK(串行时钟)管脚的上升沿捕获捕获数据。

                                CLK(SLCK):CLK为串行时钟引脚。SPI时钟引脚,为输入输出提供时钟脉冲

                                HOLD:HOLD为保持管脚,低电平有效。当CS为低电平,并且把HOLD拉低时,数据输出管脚将保持高阻态,并且会忽略数据输入管脚和时钟管脚上的信号。把HOLD管脚拉高,器件恢复正常工作。                         

                      (3)特点

                        ●灵活的 4KB 扇区结构                             SPI 串行存储器系列

                                                                                         -W25Q64:64M 位/8M 字节

                         -统一的扇区擦除(4K 字节)                 -W25Q16:16M 位/2M 字节

                        -块擦除(32K 和 64K 字节)                 -W25Q32:32M 位/4M 字节

                        -一次编程 256 字节                                 -每 256 字节可编程页

                         -至少 100,000 写/擦除周期

                         -数据保存 20 年

                        -每个设备具有唯一的 64 位 ID(1)

                        特点:Flash芯片内的数据只能由1变0,不能由0变1。

                (4)W25Q64 应用SPI

                        W25Q64支持SPI数据传输时序模式0(CPOL = 0、CPHA = 0)和模式3(CPOL = 1、CPHA = 1),模式0和模式3主要区别是当SPI主机硬件接口处于空闲状态时,SCLK的电平状态是高电平或者是低电平。对于模式0来说,SCLK处于低电平;对于模式3来说,SCLK处于高电平。不过,在这两种模式下,芯片都是在SCLK的上升沿采集输入数据,下降沿输出数据

                        W25Q64使用的数据传输格式:就是数据长度为8位大小,而且传输的时候,高位在前,低位在后

                8、SPI应用

                

#include "spi.h"

/*
Pb12  cs
pb13  sck
pb14  miso
pb15  mosi
*/
void Spi_Config(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//打开管脚时钟
	
	GPIO_InitTypeDef GPIO_InitStructure={0};//定义结构体 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15; //引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽模式
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
	SPI_InitTypeDef SPI_InitStructure = {0}; 
设置SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式---这里选用全双工模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主机模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8为的数据帧格式
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟极性设置---开始电平为低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //时钟相位设置----第一个跳变沿
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; /*就是设置 SPI 波特率预分频值----2分频这里选用*/
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
	SPI_InitStructure.SPI_CRCPolynomial = 7;  /* CRC 值计算的多项式 */
	SPI_Init(SPI2, &SPI_InitStructure);
	
	SPI_Cmd(SPI2,ENABLE);
}

uint8_t Spi_SendData(uint8_t data)
{
	uint8_t rxdata=0;
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==0);
	SPI_I2S_SendData(SPI2,data);
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==0);
	rxdata = SPI_I2S_ReceiveData(SPI2);
	return rxdata; 
}


#include "spi_flash.h"


void sFLASH_Init(void)
{
	Spi_Config();

}


void sFLASH_EraseSector(uint32_t SectorAddr)
{
  /*!< Send write enable instruction */
  sFLASH_WriteEnable();

  
  sFLASH_CS_LOW();
  
  sFLASH_SendByte(sFLASH_CMD_SE);
  
  sFLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  
  sFLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  
  sFLASH_SendByte(SectorAddr & 0xFF);
  
  sFLASH_CS_HIGH();

  /*!< Wait the end of Flash writing */
  sFLASH_WaitForWriteEnd();
}



void sFLASH_EraseBulk(void)
{
  /*!< Send write enable instruction */
  sFLASH_WriteEnable();

  /*!< Bulk Erase */
  /*!< Select the FLASH: Chip Select low */
  sFLASH_CS_LOW();
  /*!< Send Bulk Erase instruction  */
  sFLASH_SendByte(sFLASH_CMD_BE);
  /*!< Deselect the FLASH: Chip Select high */
  sFLASH_CS_HIGH();

  /*!< Wait the end of Flash writing */
  sFLASH_WaitForWriteEnd();
}


void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  /*!< Enable the write access to the FLASH */
  sFLASH_WriteEnable();

  /*!< Select the FLASH: Chip Select low */
  sFLASH_CS_LOW();
  /*!< Send "Write to Memory " instruction */
  sFLASH_SendByte(sFLASH_CMD_WRITE);
  /*!< Send WriteAddr high nibble address byte to write to */
  sFLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  /*!< Send WriteAddr medium nibble address byte to write to */
  sFLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  /*!< Send WriteAddr low nibble address byte to write to */
  sFLASH_SendByte(WriteAddr & 0xFF);

  /*!< while there is data to be written on the FLASH */
  while (NumByteToWrite--)
  {
    /*!< Send the current byte */
    sFLASH_SendByte(*pBuffer);
    /*!< Point on the next byte to be written */
    pBuffer++;
  }

  /*!< Deselect the FLASH: Chip Select high */
  sFLASH_CS_HIGH();

  /*!< Wait the end of Flash writing */
  sFLASH_WaitForWriteEnd();
}


void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

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

  if (Addr == 0) /*!< WriteAddr is sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      while (NumOfPage--)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else /*!< WriteAddr is not sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */
      {
        temp = NumOfSingle - count;

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

        sFLASH_WritePage(pBuffer, WriteAddr, temp);
      }
      else
      {
        sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
      NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

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

      while (NumOfPage--)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

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


void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  /*!< Select the FLASH: Chip Select low */
  sFLASH_CS_LOW();

  /*!< Send "Read from Memory " instruction */
  sFLASH_SendByte(sFLASH_CMD_READ);

  /*!< Send ReadAddr high nibble address byte to read from */
  sFLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /*!< Send ReadAddr medium nibble address byte to read from */
  sFLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /*!< Send ReadAddr low nibble address byte to read from */
  sFLASH_SendByte(ReadAddr & 0xFF);

  while (NumByteToRead--) /*!< while there is data to be read */
  {
    /*!< Read a byte from the FLASH */
    *pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
    /*!< Point to the next location where the byte read will be saved */
    pBuffer++;
  }

  /*!< Deselect the FLASH: Chip Select high */
  sFLASH_CS_HIGH();
}


uint32_t sFLASH_ReadID(void)
{
  uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

  /*!< Select the FLASH: Chip Select low */
  sFLASH_CS_LOW();

  /*!< Send "RDID " instruction */
  sFLASH_SendByte(0x9F);

  /*!< Read a byte from the FLASH */
  Temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);

  /*!< Read a byte from the FLASH */
  Temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);

  /*!< Read a byte from the FLASH */
  Temp2 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);

  /*!< Deselect the FLASH: Chip Select high */
  sFLASH_CS_HIGH();

  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;

  return Temp;
}


void sFLASH_StartReadSequence(uint32_t ReadAddr)
{
  /*!< Select the FLASH: Chip Select low */
  sFLASH_CS_LOW();

  /*!< Send "Read from Memory " instruction */
  sFLASH_SendByte(sFLASH_CMD_READ);

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

/**
  * @brief  Reads a byte from the SPI Flash.
  * @note   This function must be used only if the Start_Read_Sequence function
  *         has been previously called.
  * @param  None
  * @retval Byte Read from the SPI Flash.
  */
uint8_t sFLASH_ReadByte(void)
{
  return (sFLASH_SendByte(sFLASH_DUMMY_BYTE));
}



uint8_t sFLASH_SendByte(uint8_t byte)
{
  /*!< Loop while DR register in not emplty */
  while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_TXE) == RESET);

  /*!< Send byte through the SPI1 peripheral */
  SPI_I2S_SendData(sFLASH_SPI, byte);

  /*!< Wait to receive a byte */
  while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET);

  /*!< Return the byte read from the SPI bus */
  return SPI_I2S_ReceiveData(sFLASH_SPI);
}

/**
  * @brief  Sends a Half Word through the SPI interface and return the Half Word
  *         received from the SPI bus.
  * @param  HalfWord: Half Word to send.
  * @retval The value of the received Half Word.
  */
uint16_t sFLASH_SendHalfWord(uint16_t HalfWord)
{
  /*!< Loop while DR register in not emplty */
  while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_TXE) == RESET);

  /*!< Send Half Word through the sFLASH peripheral */
  SPI_I2S_SendData(sFLASH_SPI, HalfWord);

  /*!< Wait to receive a Half Word */
  while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET);

  /*!< Return the Half Word read from the SPI bus */
  return SPI_I2S_ReceiveData(sFLASH_SPI);
}

/**
  * @brief  Enables the write access to the FLASH.
  * @param  None
  * @retval None
  */
void sFLASH_WriteEnable(void)
{
  /*!< Select the FLASH: Chip Select low */
  sFLASH_CS_LOW();

  /*!< Send "Write Enable" instruction */
  sFLASH_SendByte(sFLASH_CMD_WREN);

  /*!< Deselect the FLASH: Chip Select high */
  sFLASH_CS_HIGH();
}

/**
  * @brief  Polls the status of the Write In Progress (WIP) flag in the FLASH's
  *         status register and loop until write opertaion has completed.
  * @param  None
  * @retval None
  */
void sFLASH_WaitForWriteEnd(void)
{
  uint8_t flashstatus = 0;

  /*!< Select the FLASH: Chip Select low */
  sFLASH_CS_LOW();

  /*!< Send "Read Status Register" instruction */
  sFLASH_SendByte(sFLASH_CMD_RDSR);

  /*!< 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 */
    flashstatus = sFLASH_SendByte(sFLASH_DUMMY_BYTE);

  }
  while ((flashstatus & sFLASH_WIP_FLAG) == SET); /* Write in progress */

  /*!< Deselect the FLASH: Chip Select high */
  sFLASH_CS_HIGH();
}

/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */  

/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值