GD32F30x_读写外部FLASH(GD25Q256DF)驱动程序

一、工具

  1、硬件:GD32F30x系列单片机

  2、编译环境:KEIL

  3、Flash芯片:GD25Q256DF

二、芯片介绍

  GD25Q256DF是一款256M-bit(32Mbyte)的串行Flash,使用的是SPI通讯。该芯片的页大小、扇区大小及其详细信息如下表所示:

 

 

   其它详细信息请阅读数据手册,这里不再赘述。

 

三、SPI驱动程序

  SPI驱动程序使用的是硬件SPI方式实现的。

  1、SPI引脚配置

#define SPI_CS_HIGH          {GPIO_BOP(GPIOB) = (uint32_t)GPIO_PIN_12;}
#define SPI_CS_LOW           {GPIO_BC(GPIOB) = (uint32_t)GPIO_PIN_12;}
/* 
 *@brief spi引脚配置
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static void bsp_spi1_gpio_cfg(void)
{
    rcu_periph_clock_enable(RCU_GPIOB);
    rcu_periph_clock_enable(RCU_AF);
    
    /* PB12 as NSS */
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);    
    
    /* SPI1 GPIO config: SCK/PB13, MISO/PB14, MOSI/PB15 */
    gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13 | GPIO_PIN_15);
    gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_14);
    
    SPI_CS_HIGH;
}

   2、SPI配置

/* 
 *@brief spi配置
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static void bsp_spi1_cfg(void)
{
    spi_parameter_struct spi_init_struct;
    
    rcu_periph_clock_enable(RCU_SPI1);
    
    /* SPI1 parameter config */
    spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX;
    spi_init_struct.device_mode          = SPI_MASTER;
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
    spi_init_struct.nss                  = SPI_NSS_SOFT;
    spi_init_struct.prescale             = SPI_PSC_32;
    spi_init_struct.endian               = SPI_ENDIAN_MSB;
    spi_init(SPI1, &spi_init_struct);
    
    spi_enable(SPI1);
    SPI_CS_LOW;
}

  3、SPI初始化

/* 
 *@brief spi初始化
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
void bsp_spi1_init(void)
{
    bsp_spi1_gpio_cfg();
    bsp_spi1_cfg();
}

  4、SPI读写

/* 
 *@brief spi读写
 *@param data 要发送的数据
 *@param timeout 超时时长
 *@retval 接收到的数据或者超时值0xFF
 *@author Mr.W
 *@date 2020-8-4
 */
uint8_t bsp_spi1_transmit_receive_data(uint8_t data, uint32_t timeout)
{
    while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_TBE))
    {
        if(timeout-- == 0)
            return 0xFF;        
    }
    spi_i2s_data_transmit(SPI1, data);
    while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_RBNE))
    {
        if(timeout-- == 0)
            return 0xFF;        
    }
    return spi_i2s_data_receive(SPI1);
}

四、GD25Q256DF驱动程序

   1、分别封装读写函数

/* 
 *@brief 读一个字节数据
 *@retval 读到的数据
 *@author Mr.W
 *@date 2020-8-4
 */
static uint8_t gd25q256df_read_byte(void)
{
    return bsp_spi1_transmit_receive_data(0xA5, 0xFFFFFFFF);
}

/* 
 *@brief 写一个字节数据
 *@param 要写的数据
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static void gd25q256df_write_byte(uint8_t data)
{
    bsp_spi1_transmit_receive_data(data, 0xFFFFFFFF);
}

   2、写使能和写禁止

/* 
 *@brief Write Enable
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static void gd25q256df_write_enable(void)
{
    SPI_CS_LOW;
    /* 发送写使能命令 */
    gd25q256df_write_byte(0x06);
    SPI_CS_HIGH;
}

/* 
 *@brief Write Disable
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
void gd25q256df_write_disable(void)
{
    SPI_CS_LOW;
    /* 发送写失能命令 */
    gd25q256df_write_byte(0x04);
    SPI_CS_HIGH;
}

  3、读单个寄存器

/* 
 *@brief 读单个状态寄存器
 *@param 指定寄存器命令
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static uint8_t gd25q256df_read_single_status_register(uint8_t command)
{
    uint8_t status = 0;
    
    SPI_CS_LOW;
    gd25q256df_write_byte(command&0xFF);
    status = gd25q256df_read_byte();
    SPI_CS_HIGH;
    
    return status;
}

  4、等待写结束

/* 
 *@brief 等写结束;编程、擦除和写状态寄存器后均可使用该函数
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static uint8_t gd25q256df_wait_write_end(void)
{
    uint8_t status = 0;
    uint32_t timeout = 0;
    
    SPI_CS_LOW;
    /* 发送读状态寄存器命令 */
    gd25q256df_write_byte(0x05);
    do
    {
        status = gd25q256df_read_byte();
        timeout++;
        if(timeout > GD25Q256DF_WAIT_MAX_TIME)
            return 0;    
    }while(status & 0x01);
    SPI_CS_HIGH;
    
    return 1;
}

  5、写状态寄存器

/* 
 *@brief 写状态寄存器
 *@param command 指定寄存器命令
 *@paran status 写入寄存器的状态值
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
void gd25q256df_write_status_register(uint8_t command, uint8_t status)
{
    SPI_CS_LOW;
    gd25q256df_write_byte(command&0xFF);
    gd25q256df_write_byte(status&0xFF);
    SPI_CS_HIGH;
}

  6、复位

/* 
 *@brief 复位gd25q256
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static uint8_t gd25q256df_reset(void)
{
    uint8_t status = 0;
    uint32_t timeout = 0;
        
    SPI_CS_LOW;
    /* Enable Reset (66H) */
    gd25q256df_write_byte(0x66);
    SPI_CS_HIGH;
    
    SPI_CS_LOW;
    /* Reset (99H) */
    gd25q256df_write_byte(0x99);
    SPI_CS_HIGH;
    
    do{
        /* Read Status Register-1 (05H) */
        status = gd25q256df_read_single_status_register(0x05);
        timeout++;
        if(timeout > GD25Q256DF_WAIT_MAX_TIME)
            return 0;
    }while(status == 0x01);
    
    return 1;
}

  7、写页,每一页256字节

/* 
 *@brief 写页
 *@param pdata 数据起始地址
 *@param addr 写到存储空间的起始地址
 *@param size 写入数据大小
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
static uint8_t gd25q256df_write_page(const uint8_t* pdata, uint32_t addr, uint16_t size)
{
    uint8_t ret = 0;
        
    /* 使能写 */
    gd25q256df_write_enable();
    
    SPI_CS_LOW;
    
    /* 发送写命令 */
    gd25q256df_write_byte(0x02);
    /* 发送32位地址 */
    gd25q256df_write_byte((addr & 0xFF000000) >> 24);
    gd25q256df_write_byte((addr & 0xFF0000) >> 16);
    gd25q256df_write_byte((addr & 0xFF00) >> 8);
    gd25q256df_write_byte(addr & 0xFF);
    
    while(size--)
    {
        gd25q256df_write_byte(*pdata);
        pdata++;
    }
    
    SPI_CS_HIGH;
    /* 等待写完成 */
    ret = gd25q256df_wait_write_end();    
    
    return ret;
}

  8、写扇区,每一个扇区4096字节

/* 
 *@brief 写扇区
 *@param pdata 数据起始地址
 *@param addr 写到存储空间的起始地址
 *@param size 写入数据大小
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
uint8_t gd25q256df_write_sector(const uint8_t* pdata, uint32_t addr, uint16_t size)
{
    uint8_t ret = 0;
    uint16_t page_offset = 0;
    uint16_t page_remain = 0;

    /* 计算页内偏移地址 */
    page_offset = addr%256;
    /* 计算页内剩余空间 */
    page_remain = 256 - page_offset;
    
    if(size <= page_remain){
        page_remain = size;
    }
    
    while(1)
    {
        ret = gd25q256df_write_page(pdata, addr, page_remain);
        if(page_remain != size){
            addr += page_remain;
            pdata += page_remain;
            size -= page_remain;
            if(size > 256){
                page_remain = 256;
            }
            else{
                page_remain = size;
            }
        }else{
            break;
        }
    }
    return ret;
}

  9、初始化,这里要注意因为使用的芯片存储空间较大,需设置为4字节地址模式

/* 
 *@brief gd25q256初始化
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
uint8_t gd25q256df_init(void)
{
    uint8_t ret = 0;
    uint8_t reg_status = 0;
    
    ret = gd25q256df_reset();
    /* 读状态寄存器2 */
    reg_status = gd25q256df_read_single_status_register(0x35);
    if((reg_status&0x01) == 0)
    {
        SPI_CS_LOW;
        /* Enter 4-Byte Address Mode (B7H) */
        gd25q256df_write_byte(0xB7);
        SPI_CS_HIGH;
    }
    
    return ret;
}

  10、读ID,可以验证SPI操作正常与否

/* 
 *@brief 读ID
 *@retval ID号(0xC84019)
 *@author Mr.W
 *@date 2020-8-4
 */
uint32_t gd25q256df_read_id(void)
{
    uint8_t id1, id2, id3;
    uint32_t uiID;
    
    SPI_CS_LOW;
    
    /* 发送读ID命令 */
    gd25q256df_write_byte(0x9F);
    
    id1 = gd25q256df_read_byte();
    id2 = gd25q256df_read_byte();
    id3 = gd25q256df_read_byte();
    
    SPI_CS_HIGH;
    
    uiID = (id1 << 16) | (id2 << 8) | id3;
    
    return uiID;
}

  11、读数据

/* 
 *@brief 从存储器中读数据
 *@param pdata 读到的数据起始地址
 *@param address 要读数据存放的起始地址
 *@param size 读取的数据大小
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
void gd25q256df_read_data(uint8_t* pdata, uint32_t address, uint16_t size)
{
    uint32_t i;
    
    SPI_CS_LOW;
    
    /* 发送读命令 */
    gd25q256df_write_byte(0x03);
    /* 发送32位地址 */
    gd25q256df_write_byte(address >> 24);
    gd25q256df_write_byte(address >> 16);
    gd25q256df_write_byte(address >> 8);
    gd25q256df_write_byte(address);
    /* 开始接收数据 */
    for(i = 0; i < size; i++)
    {
        pdata[i] = gd25q256df_read_byte();
    }

    SPI_CS_HIGH;
}

  12、写数据

/* 扇区缓冲区 */
uint8_t gd25q256_buffer[4096];
/* 
 *@brief 向存储器中写数据
 *@param pdata 要写数据的起始地址
 *@param address 要写数据存放的起始地址
 *@param size 要写数据大小
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
uint8_t gd25q256df_write_data(const uint8_t* pdata, uint32_t address, uint16_t size)
{
    uint8_t ret = 0;    
    uint32_t sector_pos = 0;
    uint16_t sector_offset = 0;            
    uint16_t sector_remain = 0;
    uint32_t i;
    
    /* 扇区地址 */
    sector_pos = address/4096;
    /* 计算扇区内地址偏移 */
    sector_offset = address%4096;
    /* 计算扇区内剩余空间 */
    sector_remain = 4096 - sector_offset;
    
    if(size <= sector_remain){
        sector_remain = size;
    }
    while(1)
    {
        /* 读当前扇区的所有数据 */
        gd25q256df_read_data(gd25q256_buffer, sector_pos*4096, 4096);
        for(i = 0; i < sector_remain; i++){
            if(gd25q256_buffer[sector_offset + i] != 0xFF)
                break;
        }
        if(i < sector_remain){
            /* 擦除当前扇区 */
            gd25q256df_sector_erase(sector_pos*4096);
            for(i = 0; i < sector_remain; i++){
                gd25q256_buffer[sector_offset + i] = pdata[i];
            }
            ret = gd25q256df_write_sector(gd25q256_buffer, sector_pos*4096, 4096);
        }else{
            ret = gd25q256df_write_sector(pdata, address, sector_remain);
        }
        
        if(size == sector_remain){
            break;
        }else{
            sector_pos++;
            sector_offset = 0;
            
            pdata += sector_remain;
            address += sector_remain;
            size -= sector_remain;
            if(size > 4096){
                sector_remain = 4096;
            }else{
                sector_remain = size;
            }
        }
    }
    
    return ret;
}

  13、扇区擦除

/* 
 *@brief 扇区擦除
    Any address inside the sector is a valid address for the 4k Sector Erase (SE) command.
 *@param sector_addr 扇区地址
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
uint8_t gd25q256df_sector_erase(uint32_t sector_addr)
{
    uint8_t ret = 0;
        
    /* 写使能 */
    gd25q256df_write_enable();
    
    ret = gd25q256df_wait_write_end();
    if(ret == 0)
        return ret;
    
    SPI_CS_LOW;
    
    /* 发送读命令 */
    gd25q256df_write_byte(0x20);
    /* 发送32位地址 */
    gd25q256df_write_byte((sector_addr & 0xFF000000) >> 24);
    gd25q256df_write_byte((sector_addr & 0xFF0000) >> 16);
    gd25q256df_write_byte((sector_addr & 0xFF00) >> 8);
    gd25q256df_write_byte(sector_addr & 0xFF);
    
    SPI_CS_HIGH;
    
    /* 等待擦除完成 */
    ret = gd25q256df_wait_write_end();
    
    return ret;
}

  14、整片擦除

/* 
 *@brief 整片擦除
 *@retval none
 *@author Mr.W
 *@date 2020-8-4
 */
uint8_t gd25q256df_chip_erase(void)
{
    uint8_t ret = 0;
    
    /* 写使能 */
    gd25q256df_write_enable();

    SPI_CS_LOW;
    /* 发送擦除命令 */
    gd25q256df_write_byte(0xC7);

    SPI_CS_HIGH;    
    
    /* 等待擦除完成 */
    ret = gd25q256df_wait_write_end();
    
    return ret;
}

 

 

#endif

  

posted @ 2020-11-28 13:23  不要让自己太懒  阅读(6847)  评论(0编辑  收藏  举报