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