STM32之SPI读写FLASH(W25Q64)

嵌入式 专栏收录该内容
38 篇文章 1 订阅

/*
名称: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指南者配套代码

*/

驱动程序源文件:

#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 */
评论 10 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:创作都市 设计师:CSDN官方博客 返回首页

打赏作者

BLSxiaopanlaile

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值