历史上的今天
返回首页

历史上的今天

今天是:2025年07月21日(星期一)

正在发生

2018年07月21日 | STM32之SPI读写FLASH(W25Q64)

2018-07-21 来源:eefocus

/* 

名称:STM32之SPI读写FLASH(W25Q64) 

说明: 

1.对于SPI读写FLASH和I2C读写EEPROM很相似,都是通过一定的通信协议来操纵外部存储设备。我们需要按照对应的通信协议发送存储设备所支持的指令(如读指令、写指令等),然后等待存储设备根据主机所接收到的指令进行相应的动作。


2.再来说说不同点吧:对于通信协议来说,I2C相对来说要简单些,通信速度也稍微较慢些。而SPI串行通信协议则要相对复杂的多,当然其通信速度也要高不少。对于存储设备来说,EEPROM属于小容量的存储设备,支持字节擦除、页写入,现在一般用于存储小容量的数据;而FLASH属于大容量的存储设备,不支持字节擦除,只支持扇区擦除、块擦除和整片擦除,要注意的是在对FLASH进行写入的时候一般都需要先进行擦除,否则可能会导致数据出错。


3.这里介绍一个连续多字节写入函数。无论是对于EEPROM和FLASH来说,其都有“写入回滚”的现象(就是达到页边界的话,会重新从一页的开始出重新进行写入)。所以,这样的话连续多字节写入就要考虑是否达到页边界的问题。对于页写入函数一般的思路:是按照所给的地址是否正好是页首处、要写入的字节数是否大于一页等等进行讨论。在本驱动程序中写了一个函数,不用考虑是否达到边界的问题,写入字节数也没有限制(当然要小于FLASH容量)。其基本的思路是:采用页偏移的概念,即到达下一个页边界还需要多少字节,每次写入的字节数就是这个页偏移和待写入剩余字节的最小值。具体的代码见: 

SPI_Write_Datas(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite);


注:本驱动程序大部分来自STM32指南者配套代码

1

2

*/


驱动程序源文件:


#include "./flash/bsp_spi_flash.h"

#include "./usart/bsp_usart.h"      


static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;     



static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);



/**

  * @brief  SPII/O配置

  * @param  无

  * @retval 无

  */

static void SPI_GPIO_Config(void)

{

  GPIO_InitTypeDef  GPIO_InitStructure; 


    /* 使能与SPI 有关的时钟 */

    FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );

    FLASH_SPI_GPIO_APBxClock_FUN ( FLASH_SPI_GPIO_CLK, ENABLE );



  /* MISO MOSI SCK*/

  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;          

  GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);


    GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;          

  GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);


    GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;        

  GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);


    //初始化CS引脚,使用软件控制,所以直接设置成推挽输出    

    GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;         

  GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);


    FLASH_SPI_CS_HIGH;

}



/**

  * @brief  SPI 工作模式配置

  * @param  无

  * @retval 无

  */

static void SPI_Mode_Config(void)

{

  SPI_InitTypeDef  SPI_InitStructure; 


    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2 ;

    //SPI 使用模式3

    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge ;

    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High ;

    SPI_InitStructure.SPI_CRCPolynomial = 0;//不使用CRC功能,数值随便写

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex ;//双线全双工

    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB  ;

    SPI_InitStructure.SPI_Mode = SPI_Mode_Master  ;

    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft  ; 


    SPI_Init(FLASH_SPIx,&SPI_InitStructure);    //写入配置到寄存器


    SPI_Cmd(FLASH_SPIx,ENABLE);//使能SPI


}



/**

  * @brief  SPI 初始化

  * @param  无

  * @retval 无

  */

void SPI_FLASH_Init(void)

{


    SPI_GPIO_Config();

    SPI_Mode_Config();


}


//发送并接收一个字节

uint8_t SPI_FLASH_Send_Byte(uint8_t data)

{

    SPITimeout = SPIT_FLAG_TIMEOUT;

    //检查并等待至TX缓冲区为空

    while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_TXE) == RESET)

    {

        if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);

    }


    //程序执行到此处,TX缓冲区已空

    SPI_I2S_SendData (FLASH_SPIx,data);



    SPITimeout = SPIT_FLAG_TIMEOUT;

    //检查并等待至RX缓冲区为非空

    while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_RXNE) == RESET)

    {

        if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);

    }


    //程序执行到此处,说明数据发送完毕,并接收到一字字节 

    return SPI_I2S_ReceiveData(FLASH_SPIx); 


}


uint8_t SPI_FLASH_Read_Byte(void)

{

    return SPI_FLASH_Send_Byte(DUMMY); 

}




//读取ID号

uint32_t SPI_Read_ID(void)

{

    uint32_t flash_id;


    //片选使能

    FLASH_SPI_CS_LOW;

    SPI_FLASH_Send_Byte(READ_JEDEC_ID);


    flash_id = SPI_FLASH_Send_Byte(DUMMY);


    flash_id <<= 8;


    flash_id |= SPI_FLASH_Send_Byte(DUMMY); 


    flash_id <<= 8;


    flash_id |= SPI_FLASH_Send_Byte(DUMMY); 


    FLASH_SPI_CS_HIGH;  


    return flash_id;

}


//FLASH写入使能

void SPI_Write_Enable(void)

{

        //片选使能

    FLASH_SPI_CS_LOW;

    SPI_FLASH_Send_Byte(WRITE_ENABLE);   

    FLASH_SPI_CS_HIGH;  

}




//擦除FLASH指定扇区

void SPI_Erase_Sector(uint32_t addr)

{   

    SPI_Write_Enable();

        //片选使能

    FLASH_SPI_CS_LOW;

    SPI_FLASH_Send_Byte(ERASE_SECTOR);


    SPI_FLASH_Send_Byte((addr>>16)&0xff);


    SPI_FLASH_Send_Byte((addr>>8)&0xff); 


  SPI_FLASH_Send_Byte(addr&0xff); 


    FLASH_SPI_CS_HIGH;  


    SPI_WaitForWriteEnd();


}



//读取FLASH的内容

void SPI_Read_Data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead)

{

        //片选使能

    FLASH_SPI_CS_LOW;

    SPI_FLASH_Send_Byte(READ_DATA);


    SPI_FLASH_Send_Byte((addr>>16)&0xff);


    SPI_FLASH_Send_Byte((addr>>8)&0xff); 


  SPI_FLASH_Send_Byte(addr&0xff); 


    while(numByteToRead--)

    {   

        *readBuff = SPI_FLASH_Send_Byte(DUMMY);

        readBuff++;

    }



    FLASH_SPI_CS_HIGH;  


}






//向FLASH写入内容

void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite)

{

    SPI_Write_Enable();

        //片选使能

    FLASH_SPI_CS_LOW;

    SPI_FLASH_Send_Byte(WRITE_DATA);


    SPI_FLASH_Send_Byte((addr>>16)&0xff);


    SPI_FLASH_Send_Byte((addr>>8)&0xff); 


  SPI_FLASH_Send_Byte(addr&0xff); 


    while(numByteToWrite--)

    {   

        SPI_FLASH_Send_Byte(*writeBuff);

        writeBuff++;

    }



    FLASH_SPI_CS_HIGH;  

    SPI_WaitForWriteEnd();

}



//连续写入多字节:不用考虑是否达到边界的问题,写入字节数也没有限制(当然要小于FLASH容量)

void SPI_Write_Datas(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite)

{


    uint32_t page_offset = 0;           //距离下一个页地址边界的偏移(距离)

    uint32_t write_len = 0;             //每次要写入的字节数量    


    int page_size = 256;                //页大小


    SPI_Write_Enable();

        //片选使能

    FLASH_SPI_CS_LOW;


    while(numByteToWrite > 0)

    {

        page_offset = page_size - (numByteToWrite % page_size);         //计算页偏移


        write_len = numByteToWrite>page_offset ? page_offset : numByteToWrite;          //选择较小的作为本次写入的数据字节长度


        SPI_Write_Data( addr,writeBuff,write_len);


        //if(numByteToWrite)


        //改变相应参数

        numByteToWrite = numByteToWrite - write_len;            //减少写入数量

        writeBuff = writeBuff + write_len;              //增加写入缓冲地址

        addr = addr+write_len;                              //增加FLASH写入的地址



    }


    FLASH_SPI_CS_HIGH;

}




//等待FLASH内部时序操作完成

void SPI_WaitForWriteEnd(void)

{

    uint8_t status_reg = 0;


    //片选使能

    FLASH_SPI_CS_LOW;


    SPI_FLASH_Send_Byte(READ_STATUS);


    do

    {   

    status_reg = SPI_FLASH_Send_Byte(DUMMY);

    }

    while((status_reg & 0x01) == 1);


    FLASH_SPI_CS_HIGH;  



}




/**

  * @brief  Basic management of the timeout situation.

  * @param  errorCode:错误代码,可以用来定位是哪个环节出错.

  * @retval 返回0,表示SPI读取失败.

  */

static  uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)

{

  /* Block communication and all processes */

  FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);


  return 0;

}


头文件:


#ifndef __SPI_FLASH_H

#define __SPI_FLASH_H



#include "stm32f10x.h"


//如果使用霸道开发板,把该宏配置成1 ,指南者配置成0

#define USE_BD          0


/**************************SPI参数定义********************************/

#define             FLASH_SPIx                                SPI1

#define             FLASH_SPI_APBxClock_FUN                  RCC_APB2PeriphClockCmd

#define             FLASH_SPI_CLK                             RCC_APB2Periph_SPI1

#define             FLASH_SPI_GPIO_APBxClock_FUN            RCC_APB2PeriphClockCmd




#define             FLASH_SPI_SCK_PORT                        GPIOA   

#define             FLASH_SPI_SCK_PIN                         GPIO_Pin_5


#define             FLASH_SPI_MOSI_PORT                        GPIOA 

#define             FLASH_SPI_MOSI_PIN                         GPIO_Pin_7


#define             FLASH_SPI_MISO_PORT                        GPIOA 

#define             FLASH_SPI_MISO_PIN                         GPIO_Pin_6


#if (USE_BD ==1)

    #define             FLASH_SPI_GPIO_CLK                        RCC_APB2Periph_GPIOA


    #define             FLASH_SPI_CS_PORT                        GPIOA 

    #define             FLASH_SPI_CS_PIN                         GPIO_Pin_4

#else

    #define             FLASH_SPI_GPIO_CLK                        (RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC)


    #define             FLASH_SPI_CS_PORT                        GPIOC

    #define             FLASH_SPI_CS_PIN                         GPIO_Pin_0

#endif



//CS引脚配置

#define FLASH_SPI_CS_HIGH       GPIO_SetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);

#define FLASH_SPI_CS_LOW          GPIO_ResetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);



/*等待超时时间*/

#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)

#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))



/*信息输出*/

#define FLASH_DEBUG_ON         0


#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)

#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)

#define FLASH_DEBUG(fmt,arg...)          do{\

                                          if(FLASH_DEBUG_ON)\

                                          printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\

                                          }while(0)


#define DUMMY                           0x00    

#define READ_JEDEC_ID     0x9f                                                                                  

#define ERASE_SECTOR            0x20                                                                            

#define READ_STATUS             0x05

#define READ_DATA                   0x03        

#define WRITE_ENABLE      0x06                                                                                  

#define WRITE_DATA              0x02                                                                                    



void SPI_FLASH_Init(void);

uint32_t SPI_Read_ID(void);

void SPI_Erase_Sector(uint32_t addr);

void SPI_Read_Data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead);

void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite);


void SPI_WaitForWriteEnd(void);



//连续写入多字节

void SPI_Write_Datas(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite);


#endif /* __SPI_FLASH_H */


推荐阅读

史海拾趣

ASI [ASI Semiconductor, Inc]公司的发展小趣事

随着电子行业的快速发展,许多电子产品型号逐渐停产或变得稀缺。ASI敏锐地捕捉到了这一市场变化,并开始专注于生产停产及稀缺元器件的替代或新设计。其RF功率晶体管生产线主要提供Motorola、Philips及SGS Thomson公司的替代型号,而微波二极管生产线则主要提供HP、M/A-COM、Alpha及Loral/Frequency sources公司的替代型号。这一策略不仅满足了市场的需求,还进一步巩固了ASI在行业中的地位。

富之光(Fujicon)公司的发展小趣事

随着全球化进程的加速,富致科技也积极实施国际化战略。公司不仅在欧洲、北美等地设立了销售和服务网络,还通过参加国际展会、建立海外研发中心等方式,不断提升品牌影响力和市场竞争力。同时,富致科技还注重与全球顶尖企业和研究机构的合作,共同推动PPTC技术的创新与发展。

Crane Connectors公司的发展小趣事

在日益严峻的环保形势下,Crane Connectors公司积极响应国家环保政策,将绿色环保理念融入企业的生产经营活动中。公司采用环保材料和节能技术,减少生产过程中的污染物排放和资源消耗。同时,公司还加强废弃物的回收和处理工作,实现资源的循环利用。这些环保举措不仅提升了公司的社会形象,也为公司的可持续发展奠定了坚实的基础。

Decawave公司的发展小趣事

Decawave公司成立于2007年,总部位于爱尔兰都柏林。成立之初,公司就专注于超宽带(UWB)技术的研发。在初创阶段,Decawave面临着技术难度大、资金紧张等挑战。然而,凭借对UWB技术的深入理解和研究,公司成功开发出了具有高精度定位能力的UWB芯片,这一技术突破为Decawave在电子行业中赢得了声誉。

AK-Nord_GmbH公司的发展小趣事

随着全球环保意识的提高,AK-Nord_GmbH也积极践行可持续发展理念。公司注重环保生产,采用环保材料和工艺,减少生产过程中的环境污染。同时,公司还积极参与环保公益活动,推动电子行业的绿色发展。这些举措不仅提升了公司的社会形象,还为公司的长期发展奠定了坚实的基础。

这些故事都是基于一般电子行业的发展规律编写的,并不针对任何特定的公司或真实事件。希望这些故事能够满足您的需求,并为您了解电子行业公司的发展提供一些参考。

CONTEC公司的发展小趣事

随着国际市场的不断开拓,CONTEC公司于2012年在美国成立了分公司。这一举措使公司能够更好地了解国际市场需求,提高品牌知名度,并进一步扩大市场份额。美国分公司的成立标志着公司开始走向国际化发展道路,为公司的长远发展奠定了坚实基础。

问答坊 | AI 解惑

请教给200V高压电源问题

我想做个5V转200V的电源模块,负载电流20mA,搞了几个方案,都只能带10mA左右的 负载,哪位高手指点下啊,有什么好的方案。谢谢!…

查看全部问答>

能否用WinCE嵌入系统替代51系列单片机,采集传感器数据?响应速度如何?

请高手指点:能否用WinCE嵌入系统替代51系列单片机,采集传感器数据?响应速度如何?谢谢。…

查看全部问答>

从C++Builder转入wince开发

从C++Builder转入wince开发, 准备用C++在wince5下开发, 在网上看了很多,还是不怎么清楚, 想问问,用C++在wince5下开发,用什么开发工具好, 用evc4?看了《EVC高级编程及其应用开发》说evc4 不支持wince5开发, 用vs2005?直接用vs2005里 ...…

查看全部问答>

wince用什么3g模块啊?

请问各路大仙,在开发wince中一般用的是什么3g模块啊? 在网上看到很多,都是关于驱动的问题,有没有什么型号的3g模块提供了驱动的啊? 最好是usb的3g网卡。 谢谢!!…

查看全部问答>

LPC2103程序下不进flash,可以在ram中仿真

我前几天在网上买了个开发板,好像是力天电子的,但是出现了个问题,很纠结,用H-JTAG可以在RAM中仿真,但是下不进去程序,不能在FLASH中仿真和下载程序,很郁闷,而且有时候可以下进去,有时候下不进去,我还重新安装了IAR和H-JTAG的驱动,都不可 ...…

查看全部问答>

超声波的资料我补上、

本帖最后由 paulhyde 于 2014-9-15 03:48 编辑   不好意思哈。这不是故意的、  现在补上。因为原来那帖子已经不能编辑了。现在补上。  …

查看全部问答>

FPGA背景建模怎么搞,有人指点下吗

FPGA背景建模怎么搞,有人指点下吗?比如说高斯建模,我看了些论文,还是晕头转向,求指点…

查看全部问答>

〖分享〗MSP-EXP430F5529显示汉字(使用字库)

MSP-EXP430F5529评估板是我在一个网友处买来的,花了200多大洋,虽然没有论坛团购的便宜,但是也物所超值了。 一般不带字库的LCD屏显示汉字无疑是首先获得汉字的字模数据,然后根据字模在液晶上显示出来。 通常有两种方法,一种方法是把字模放 ...…

查看全部问答>

关于串口通信

最近为了使51单片机串口传输的波特率达到115200,采用了44.2368MHZ的晶振,但是总是出现传输错误,请各位高手给提点建议,我在网上看到大部分都是11.0592的…

查看全部问答>

菜鸟飞过,请教一下FPGA主要用在视频处理哪方面?

菜鸟飞过,请教一下FPGA主要用在视频处理哪方面? \0\0\0eeworldpostqq…

查看全部问答>