一、工具
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