SPI专题(二)——STM32驱动FLASH(W25Q64)

最新更新时间:2019-01-09来源: eefocus关键字:SPI  STM32  驱动FLASH  W25Q64 手机看文章 扫描二维码
随时随地手机看文章

1.硬件连接


W25Q64 将 8M 的容量分为 128 个块(Block),每个块大小为 64K 字节,每个块又分为 16个扇区(Sector),每个扇区 4K 个字节。 W25Q64 的最少擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。操作需要给 W25Q64 开辟一个至少 4K 的缓存区,对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。


这里写图片描述


W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,W25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M)。


1.1 硬件连接


与 STM32 的引脚连接如下:这里是使用SPI1配置。


这里写图片描述



STM32引脚 对应SPI功能

PA2 片选CS

PA5 时钟SCK

PA6 MISO

PA7 MOSI

STM32 的 SPI 功能很强大, SPI 时钟最多可以到 18Mhz,支持 DMA,可以配置为 SPI 协议或者 I2S 协议(仅大容量型号支持)。


1.2 SPI通讯的通讯时序


SPI协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。


这里写图片描述


这是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK的每个时钟周期 MOSI和 MISO传输一位数据。


1.通讯的起始和停止信号


在图中的标号1处,NSS 信号线由高变低,是 SPI 通讯的起始信号。NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的标号6处,NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。


2.数据有效性


SPI使用 MOSI及 MISO信号线来传输数据,使用 SCK信号线进行数据同步。MOSI及MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI通讯设备之间使用同样的协定,一般都会采用图中的 MSB先行模式。


观察图中的2345标号处,MOSI及 MISO的数据在 SCK的上升沿期间变化输出,在SCK 的下降沿时被采样。即在 SCK 的下降沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI及MISO为下一次表示数据做准备。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。


1.3 STM32 SPI外设


STM32 的 SPI 外设可用作通讯的主机及从机,支持最高的 SCK 时钟频率为 f pclk /2(STM32F103型号的芯片默认 f pclk1 为 72MHz,f pclk2 为 36MHz),完全支持 SPI协议的 4种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工(前面小节说明的都是这种模式)、双线单向以及单线模式。


SPI架构:


这里写图片描述


通讯引脚 :


SPI的所有硬件架构都从图中左侧 MOSI、MISO、SCK及 NSS线展开的。STM32芯片有多个SPI外设,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。


2.软件配置

这里使用 STM32 的 SPI1 的主模式,SPI 相关的库函数和定义分布在文件 stm32f10x_spi.c 以及头文件 stm32f10x_spi.h 中。


2.1配置相关引脚的复用功能,使能 SPI1 时钟

第一步就要使能 SPI1 的时钟, SPI1 的时钟通过 APB2ENR 的第 12 位来设置。其次要设置 SPI1 的相关引脚为复用输出,这样才会连接到 SPI1 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PA5、 6、 7 这 3 个(SCK、 MISO、 MOSI, CS 使用软件管理方式),所以设置这三个为复用 IO。



    GPIO_InitTypeDef GPIO_InitStructure;


    RCC_APB2PeriphClockCmd( RCC_APB2P%riphGPHOA|RCC_APB2Periph_SPI1%r52C%521ENABLE ); 


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);


    GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);


2.2初始化 SPI1,设置 SPI1 工作模式

接下来初始化 SPI1,设置 SPI1 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是LSB 在前)。这在库函数中是通过 SPI_Init 函数来实现。 

函数原型:


void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);


第一个参数是 SPI 标号,第二个参数结构体类型 SPI_InitTypeDef 为相关属性设置。


SPI_InitTypeDef 的定义如下:


typedef struct

{

uint16_t SPI_Direction;

uint16_t SPI_Mode;

uint16_t SPI_DataSize;

uint16_t SPI_CPOL;

uint16_t SPI_CPHA;

uint16_t SPI_NSS;

uint16_t SPI_BaudRatePrescaler;

uint16_t SPI_FirstBit;

uint16_t SPI_CRCPolynomial;

}SPI_InitTypeDef;


参数 解释

SPI_Direction 设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式

SPI_Mode 设置 SPI 的主从模式,主机模式 (SPI_Mode_Master),从机模式 (PI_Mode_Slave)。

SPI_DataSiz 数据为 8 位还是 16 位帧格式选择项。SPI_DataSize_8b(8 位),SPI_DataSize_16b (16位)

SPI_CPOL 设置时钟极性

SPI_CPHA 设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以为第一个或者第二个条边沿采集

SPI_NSS 设置 NSS 信号由硬件(NSS 管脚)还是软件控制

SPI_BaudRatePrescaler 设置 SPI 波特率预分频值也就是决定 SPI 的时钟的参数 ,从不分频道 256 分频 8 个可选值 ,选择 256 分频值SPI_BaudRatePrescaler_256, 传输速度为 36M/256=140.625KHz。

SPI_FirstBit 设置数据传输顺序是 MSB 位在前还是 LSB 位在前。SPI_FirstBit_MSB (高位在前)

SPI_CRCPolynomial 设置 CRC 校验多项式,提高通信可靠性,大于 1 即可

初始化的范例格式为:


    SPI_InitTypeDef  SPI_InitStructure;


SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工

    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;       //设置SPI工作模式:设置为主SPI

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;       //设置SPI的数据大小:SPI发送接收8位帧结构

    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;     //选择了串行时钟的稳态:时钟悬空高

    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;    //数据捕获(采样)于第二个时钟沿

    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;       //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制

    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;        //定义波特率预分频的值:波特率预分频值为256

    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始

    SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRC值计算的多项式

    SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器


2.3使能 SPI1

初始化完成之后使能 SPI1 通信,在使能 SPI1 之后,就可以开始 SPI 通讯了。使能 SPI1 的方法为:


SPI_Cmd(SPI1, ENABLE); //使能 SPI 外设


2.4 SPI 传输数据

通信接口需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:


void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);


往 SPIx 数据寄存器写入数据 Data,从而实现发送。


固件库提供的接受数据函数原型为:


uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;


这从 SPIx 数据寄存器读出接收到的数据。


2.5查看 SPI 传输状态

在 SPI 传输过程中,要判断数据是否传输完成,发送区是否为空等等状态,通过函数 SPI_I2S_GetFlagStatus 实现的,判断发送是否完成的方法是:


SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);


3.软件设计

3.1 SPI实现


SPI_InitTypeDef  SPI_InitStructure;


void SPI1_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;


    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE ); 


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);


    GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);


    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工

    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;       //设置SPI工作模式:设置为主SPI

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;       //设置SPI的数据大小:SPI发送接收8位帧结构

    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;     //选择了串行时钟的稳态:时钟悬空高

    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;    //数据捕获(采样)于第二个时钟沿

    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;       //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制

    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;        //定义波特率预分频的值:波特率预分频值为256

    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始

    SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRC值计算的多项式

    SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器


    SPI_Cmd(SPI1, ENABLE); //使能SPI外设


    SPI1_ReadWriteByte(0xff);//启动传输      

}   

/************************************************/

//SPI 速度设置函数

//SpeedSet:

//SPI_BaudRatePrescaler_2   2分频   (SPI 36M@sys 72M)

//SPI_BaudRatePrescaler_8   8分频   (SPI 9M@sys 72M)

//SPI_BaudRatePrescaler_16  16分频  (SPI 4.5M@sys 72M)

//SPI_BaudRatePrescaler_256 256分频 (SPI 281.25K@sys 72M)


void SPI1_SetSpeed(u8 SpeedSet)

{

    SPI_InitStructure.SPI_BaudRatePrescaler = SpeedSet ;

    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1,ENABLE);


/************************************************/

//SPIx 读写一个字节

//TxData:要写入的字节

//返回值:读取到的字节

u8 SPI1_ReadWriteByte(u8 TxData)

{       

    u8 retry=0;                 

    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位

        {

        retry++;

        if(retry>200)return 0;

        }             

    SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据

    retry=0;


    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位

        {

        retry++;

        if(retry>200)return 0;

        }                               

    return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据                     

}


3.2 Flash读写


u16 SPI_FLASH_TYPE=W25Q64;//默认就是25Q64

//4Kbytes为一个Sector

//16个扇区为1个Block

//W25X16

//容量为2M字节,共有32个Block,512个Sector 


//初始化SPI FLASH的IO口

void SPI_Flash_Init(void)

{


    GPIO_InitTypeDef GPIO_InitStructure;


  RCC_APB2PeriphClockCmd(   RCC_APB2Periph_GPIOA, ENABLE );


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;  //SPI CS

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //复用推挽输出

    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_SetBits(GPIOA,GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4);

    SPI1_Init();           //初始化SPI

    SPI1_SetSpeed(SPI_BaudRatePrescaler_4); //设置为18M时钟,高速模式

    SPI_FLASH_TYPE=SPI_Flash_ReadID();//读取FLASH ID.

}  

/************************************************/

//读取SPI_FLASH的状态寄存器

//BIT7  6   5   4   3   2   1   0

//SPR   RV  TB BP2 BP1 BP0 WEL BUSY

//SPR:默认0,状态寄存器保护位,配合WP使用

//TB,BP2,BP1,BP0:FLASH区域写保护设置

//WEL:写使能锁定

//BUSY:忙标记位(1,忙;0,空闲)

//默认:0x00

u8 SPI_Flash_ReadSR(void)   

{  

    u8 byte=0;   

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_ReadStatusReg);    //发送读取状态寄存器命令    

    byte=SPI1_ReadWriteByte(0Xff);             //读取一个字节  

    SPI_FLASH_CS=1;                            //取消片选     

    return byte;   


/************************************************/

//写SPI_FLASH状态寄存器

//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!

void SPI_FLASH_Write_SR(u8 sr)   

{   

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_WriteStatusReg);   //发送写取状态寄存器命令    

    SPI1_ReadWriteByte(sr);               //写入一个字节  

    SPI_FLASH_CS=1;                            //取消片选             

}   


/************************************************/

//SPI_FLASH写使能  

//将WEL置位   

void SPI_FLASH_Write_Enable(void)   

{

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_WriteEnable);      //发送写使能  

    SPI_FLASH_CS=1;                            //取消片选             


/************************************************/

//SPI_FLASH写禁止  

//将WEL清零  

void SPI_FLASH_Write_Disable(void)   

{  

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_WriteDisable);     //发送写禁止指令    

    SPI_FLASH_CS=1;                            //取消片选             

}               


/************************************************/

//读取芯片ID W25X16的ID:0XEF14

u16 SPI_Flash_ReadID(void)

{

    u16 Temp = 0;     

    SPI_FLASH_CS=0;                 

    SPI1_ReadWriteByte(0x90);//发送读取ID命令     

    SPI1_ReadWriteByte(0x00);       

    SPI1_ReadWriteByte(0x00);       

    SPI1_ReadWriteByte(0x00);                  

    Temp|=SPI1_ReadWriteByte(0xFF)<<8;  

    Temp|=SPI1_ReadWriteByte(0xFF);  

    SPI_FLASH_CS=1;                 

    return Temp;

}               


/************************************************/

//读取SPI FLASH  

//在指定地址开始读取指定长度的数据

//pBuffer:数据存储区

//ReadAddr:开始读取的地址(24bit)

//NumByteToRead:要读取的字节数(最大65535)

void SPI_Flash_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   

    u16 i;                                                      

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_ReadData);         //发送读取命令   

    SPI1_ReadWriteByte((u8)((ReadAddr)>>16));  //发送24bit地址    

    SPI1_ReadWriteByte((u8)((ReadAddr)>>8));   

    SPI1_ReadWriteByte((u8)ReadAddr);   

    for(i=0;i

    { 

        pBuffer[i]=SPI1_ReadWriteByte(0XFF);   //循环读数  

    }

    SPI_FLASH_CS=1;                            //取消片选             

}  


/************************************************/

//SPI在一页(0~65535)内写入少于256个字节的数据

//在指定地址开始写入最大256字节的数据

//pBuffer:数据存储区

//WriteAddr:开始写入的地址(24bit)

//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!   

void SPI_Flash_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)

{

    u16 i;  

    SPI_FLASH_Write_Enable();                  //SET WEL 

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_PageProgram);      //发送写页命令   

    SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址    

    SPI1_ReadWriteByte((u8)((WriteAddr)>>8));   

    SPI1_ReadWriteByte((u8)WriteAddr);   

    for(i=0;i

        {

            SPI1_ReadWriteByte(pBuffer[i]);//循环写数  

        }

    SPI_FLASH_CS=1;                            //取消片选 

    SPI_Flash_Wait_Busy();                     //等待写入结束


/************************************************/

//无检验写SPI FLASH 

//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!

//具有自动换页功能 

//在指定地址开始写入指定长度的数据,但是要确保地址不越界!

//pBuffer:数据存储区

//WriteAddr:开始写入的地址(24bit)

//NumByteToWrite:要写入的字节数(最大65535)

//CHECK OK

void SPI_Flash_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   

{                    

    u16 pageremain;    

    pageremain=256-WriteAddr%256; //单页剩余的字节数                

    if(NumByteToWrite<=pageremain)

        pageremain=NumByteToWrite;//不大于256个字节

    while(1)

    {      

        SPI_Flash_Write_Page(pBuffer,WriteAddr,pageremain);

        if(NumByteToWrite==pageremain)

            break;//写入结束了

        else //NumByteToWrite>pageremain

        {

            pBuffer+=pageremain;

            WriteAddr+=pageremain;  


            NumByteToWrite-=pageremain;           //减去已经写入了的字节数

            if(NumByteToWrite>256)

                pageremain=256; //一次可以写入256个字节

            else pageremain=NumByteToWrite;       //不够256个字节了

        }

    };      


/************************************************/

//写SPI FLASH  

//在指定地址开始写入指定长度的数据

//该函数带擦除操作!

//pBuffer:数据存储区

//WriteAddr:开始写入的地址(24bit)

//NumByteToWrite:要写入的字节数(最大65535)          

u8 SPI_FLASH_BUF[4096];//一个扇区大小

void SPI_Flash_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   

    u32 secpos;

    u16 secoff;

    u16 secremain;     

    u16 i;    


    secpos=WriteAddr/4096;//扇区地址 0~511 for w25x16

    secoff=WriteAddr%4096;//在扇区内的偏移

    secremain=4096-secoff;//扇区剩余空间大小   


    if(NumByteToWrite<=secremain)

        secremain=NumByteToWrite;//不大于4096个字节

    while(1) 

    {   

        SPI_Flash_Read(SPI_FLASH_BUF,secpos*4096,4096);//读出整个扇区的内容

        for(i=0;i

        {

            if(SPI_FLASH_BUF[secoff+i]!=0XFF)break;//需要擦除     

        }

        if(i

        {

            SPI_Flash_Erase_Sector(secpos);//擦除这个扇区

            for(i=0;i

            {

                SPI_FLASH_BUF[i+secoff]=pBuffer[i];   

            }

            SPI_Flash_Write_NoCheck(SPI_FLASH_BUF,secpos*4096,4096);//写入整个扇区  


        }

        else 

            SPI_Flash_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.                 

        if(NumByteToWrite==secremain)

            break;//写入结束了

        else//写入未结束

        {

            secpos++;//扇区地址增1

            secoff=0;//偏移位置为0    


            pBuffer+=secremain;  //指针偏移

            WriteAddr+=secremain;//写地址偏移       

            NumByteToWrite-=secremain;              //字节数递减

            if(NumByteToWrite>4096)secremain=4096;  //下一个扇区还是写不完

            else secremain=NumByteToWrite;          //下一个扇区可以写完了

        }    

    };       

}


/************************************************/

//擦除整个芯片

//整片擦除时间:

//W25X16:25s 

//W25X32:40s 

//W25X64:40s 

//等待时间超长...

void SPI_Flash_Erase_Chip(void)   

{                                             

    SPI_FLASH_Write_Enable();                  //SET WEL 

    SPI_Flash_Wait_Busy();   

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_ChipErase);        //发送片擦除命令  

    SPI_FLASH_CS=1;                            //取消片选             

    SPI_Flash_Wait_Busy();                     //等待芯片擦除结束

}   


/************************************************/

//擦除一个扇区

//Dst_Addr:扇区地址 0~511 for w25x16

//擦除一个山区的最少时间:150ms

void SPI_Flash_Erase_Sector(u32 Dst_Addr)   

{   

    Dst_Addr*=4096;

    SPI_FLASH_Write_Enable();                  //SET WEL     

    SPI_Flash_Wait_Busy();   

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_SectorErase);      //发送扇区擦除指令 

    SPI1_ReadWriteByte((u8)((Dst_Addr)>>16));  //发送24bit地址    

    SPI1_ReadWriteByte((u8)((Dst_Addr)>>8));   

    SPI1_ReadWriteByte((u8)Dst_Addr);  

    SPI_FLASH_CS=1;                            //取消片选             

    SPI_Flash_Wait_Busy();                     //等待擦除完成

}  


/************************************************/

//等待空闲

void SPI_Flash_Wait_Busy(void)   

{   

    while ((SPI_Flash_ReadSR()&0x01)==0x01);   // 等待BUSY位清空

}  


/************************************************/

//进入掉电模式

void SPI_Flash_PowerDown(void)   

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_PowerDown);        //发送掉电命令  

    SPI_FLASH_CS=1;                            //取消片选             

    delay_us(3);                               //等待TPD  

}   


/************************************************/

//唤醒

void SPI_Flash_WAKEUP(void)   

{  

    SPI_FLASH_CS=0;                            //使能器件   

    SPI1_ReadWriteByte(W25X_ReleasePowerDown);   //  send W25X_PowerDown command 0xAB    

      SPI_FLASH_CS=1;                            //取消片选               

    delay_us(3);                               //等待TRES1

}   



参考:

1.原子库函数手册


2.SPI—读写串行 FLASH

--------------------- 

作者:wwt18811707971 

来源:CSDN 

原文:https://blog.csdn.net/wwt18811707971/article/details/77756312 

版权声明:本文为博主原创文章,转载请附上博文链接!


关键字:SPI  STM32  驱动FLASH  W25Q64 编辑:什么鱼 引用地址:SPI专题(二)——STM32驱动FLASH(W25Q64)

上一篇:STM32学习笔记一一输入捕获
下一篇:IIC专题(二)——STM32驱动AT24C02

推荐阅读最新更新时间:2023-08-08 15:02

DSP与STM32区别
关于DSP和普通51 AVR还有STM32的区别 DSP是为运算而生的芯片,他最强大的地方就在与它的数**算性能,那是由它的指令集支持的。那些拿DSP和STM32比较的,省省吧,如果你两者都熟悉你就知道根本没啥好比的, 如果我需要很多高级的接口,比如以太网和USB,那么我自然选STM32,如果我需要实现一些算法,那肯定会选DSP。如果你对运算速度不敏感,反正72M的速度已经比原先单片机快很多了,那当然是看你熟悉哪个,哪个价格比较好,支持比较好。 从51 AVR到DSP最大的障碍   1、应该是编译环境吧,TI的DSP都用的是CCS,CCS界面和原先IAR区别不是一般的大,比如程序导入,比如观察变量,比如烧写FLASH
[单片机]
关于STM32的定时器问题集锦
1、定时器外部计数功能 问:STM32处理器的定时器可以配置为对外部脉冲计数方式,其中一种方式是通过TIM的ETR引脚(外部触发引脚),另外一种方式是通过TIM的CH1或者CH2引脚来输入。现在我不明白这两种方式有什么区别,两种方式都能对外部脉冲计数,那么设置外部触发方式的目的指什么? 答:根据设计电路来使用不同的方法,他们最大的区别就是引脚不同,但是实现的功能是一样的。 2、TIM2用于捕获,如何调整TIM2的时钟? 问:TIM2用于捕获,如何调整TIM2的时钟?想调低TIM2的时钟频率,以减小计数器的值,避免溢出。 答:可在中断函数里修改配置。不过我想知道你的具体目的是什么 答:因被捕获脉冲频率很宽,有
[单片机]
安全启动 - STM32防内外攻击技术
在 STM32 系列里,STM32L0、STM32L4,提供了 Firewall 硬件。Firewall 硬件提供了调用门 Callgate 技术。也就是说, 对于安全敏感的代码与操作,只有一个入口 。任何不通过这个接口,而对受保护的代码与数据进行访问,则会导致系统重启。如同字面意思,Firewall 形成了一个围墙,形成了一个城堡,同时提供一个入口,来提供运行时内部的保密、完整、可靠、可用特性。 通过 Firewall 单一入口的功能,可以形成对外的简单接口。该接口简单,所以安全敏感的操作细节就不会对外泄露。因为外界只能通过接口访问,那么在内部操作之中所使用到的密钥也不会泄露。 STM32 Firewall 通过启动代码激活后,
[单片机]
STM32 CubeMX按键中断
一、GPIO 8种工作模式 输入模式: 1. GPIO_Mode_AIN 模拟输入 2. GPIO_Mode_IN_FLOATING 浮空输入 3. GPIO_Mode_IPD 下拉输入 4. GPIO_Mode_IPU 上拉输入 输出模式: 5. GPIO_Mode_Out_OD 开漏输出 6. GPIO_Mode_Out_PP 推挽输出 7. GPIO_Mode_AF_OD 复用开漏输出 8. GPIO_Mode_AF_PP 复用推挽输出 施密特触发器:当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;当输入在正负向阈值电压之间,输出不改变,只有当输入电压发生足够的变化
[单片机]
<font color='red'>STM32</font> CubeMX按键中断
STM32存储器 — <1> 关于STM32的存储器
当我们在完成某一个实验,当我们正庆幸的时候,我们不由得产生一种不安的想法 我们是否已经少许明白其中的种种细节? 尤其,当我们所有的事情都依赖于编译环境或Firmware,抑或他人的程序,而自己仅仅是Copy和Modify,以致Using。当你还是一个初学者的时候,或许不会太过于关注于此,但是要想提高自己对单片机、处理器原理的理解,并且希望走得更远的时候,您就需要关注更为详细的内部知识,您需要明白编译环境、Firmware等为你的默默贡献。 1 STM32系统结构 要想深刻理解STM32的存储器,需要首先知道STM32的系统结构。 如Figure 1,是STM32系统结构框图。
[单片机]
基于Xmodem的STM32的IAP升级【转】
实验平台:windows7 + STM32F103ZET6 实验目的:你不需要任何烧录工具,就可以对你的产品进行远程升级代码! 1. Xmodem协议简介 2. IAP编程原理 3. Boot与App程序设计 4. 实验步骤 5. 远程升级应用 1.modem 协议 串行通信的文件传输协议主要有:Xmodem、Ymodem、Zmodem和KERMIT等。 Xmodem 协议传输由接收程序和发送程序完成。先由接收程序发送协商字符,协商校验方式,协商通过之后发送程序就开始发送数据包,接收程序接收到完整的一个数据包之后, 按照协商的方式对数据包进行校验。校验通过之后发送确认字符,然后发送程序继续发送下一包;如果校验失败,则发送否认字
[单片机]
基于Xmodem的<font color='red'>STM32</font>的IAP升级【转】
STM32:STM32学习记录1:MDK基本数据类型及代码优化
大概一年前开始接触STM32,当时就被它的库函数开发所吸引,但是迫于各种压力放弃了学习,一直在使用所谓稳定的单片机来开发(忍不住要吐槽),现在终于有时间了,开始自己的兴趣之旅喽!! 现在网上有各种大牛的经验文档使我受益匪浅,也感谢室友的无私帮助!!! 大概看了一下大牛的经验文档,好像没有一个提到MDK的基本数据类型的,自己找找看在MDK的帮助里面有。 打开MDK----- help---- uVision help ---- RealView Compiler Reference Guide ---- C and C++ implementation details ---- C and C++ implementat
[单片机]
<font color='red'>STM32</font>:<font color='red'>STM32</font>学习记录1:MDK基本数据类型及代码优化
关于STM32的除以0运算问题
有人使用STM32G4系列芯片开发产品。他发现程序中如果遇到除以0的操作时,会跑进出错异常中断而影响程序运行。他想知道能否通过设置,即使发生除以0操作也不让程序跑进异常中断,并期望此时的除法运行结果【也就是商】直接等于当前变量类型所支持的最大值,比如,若被除数为16位变量,则经过该除以零操作后直接为其赋值为0xffff。【实际应用中客户的需求往往也是五彩斑斓的。^_^】 事实上是否可以如该STM32用户所愿呢?我们不妨一起看看。 首先,这个问题不属于STM32外设相关的,而是内核相关的。客户选用的是Cortex M4的内核STM32芯片,那我们就从M4内核手册中寻找相关内容。 我们通过查看ARM M4的内核手册,可以看
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2023 EEWORLD.com.cn, Inc. All rights reserved