20.1 實驗內(nèi)容
通過本實驗主要學習以下內(nèi)容:
? SPI通信協(xié)議,參考19.2.1東方紅開發(fā)板使用手冊
? GD32F303 SPI操作方式,參考19.2.2東方紅開發(fā)板使用手冊
? NAND FLASH基本原理
? SPI NAND介紹
? 使用GD32F303 SPI接口實現(xiàn)對GD5F1GQ5UEYIGY的讀寫操作
20.2 實驗原理
20.2.1 NAND FLASH基本原理
NAND Flash和NOR Flash都是兩種非易失性存儲器,其讀寫速度、讀寫方式,存儲區(qū)結構、成本、容量、擦寫壽命都有很大區(qū)別。NAND在壽命、速度、讀寫方式上都不如NOR,但在成本和容量上有很大區(qū)別,故而決定了大容量數(shù)據(jù)存儲是NAND的主要應用領域,而快速啟動、快速數(shù)據(jù)讀取等場景是NOR的主要應用領域。而SPI是目前NAND和NOR的主要通信接口形式,降低了器件體積,標準化了器件接口。
? NAND Flash結構示例
如上圖所示,以GD5F1GQ5UEYIGY為例,一個1Gb的存儲結構下是由1024個block組成,每個block又64page組成,每個page是2K Main Area+Spare Area(ECC ON:64B;ECC OFF:128B)組成。
NAND的擦除單位是blocks,寫入單位是page,所以尋址的方式上和nor是有本質區(qū)別的,需要按blocks、page、page字節(jié)偏移地址進行一個數(shù)據(jù)的尋址。
20.2.2 SPI NAND介紹
SPI NAND簡化了NAND的接口設計和尺寸,SPI接口更是降低了主控對接口的要求,同時內(nèi)置ECC。下圖是GD5F1GQ5UEYIGY的命令表,常用的命令為擦除、編程、讀取命令。
? block擦除命令
? 編程
? 編程流程
i. 先用數(shù)據(jù)緩存寫入指令將數(shù)據(jù)寫入緩沖區(qū)
ii. 然后發(fā)送寫使能命令,并確認寫使能成功
iii. 然后發(fā)送數(shù)據(jù)載入命令執(zhí)行緩沖區(qū)數(shù)據(jù)到FLASH的寫
iv. 最后查詢讀寄存器確認P_FAIL是否有錯,OIP是否完成
注意(84h/C4h/34h) 和(FFh) 指令是不會清除緩存中的內(nèi)容的,所以下次編程時要注意是否緩存區(qū)都是需要更新的數(shù)據(jù),所以必須是一次更新整個緩沖區(qū),不要部分更新。
編程page地址按照塊的順序
? 數(shù)據(jù)緩存寫入命令
? 數(shù)據(jù)載入命令
? 讀取
? 讀取流程
i. 讀需要先通過讀cache命令從FLASH中讀出數(shù)據(jù)到緩存中
ii. 然后通過讀cache指令從緩沖區(qū)中開始讀出數(shù)據(jù)
讀到2048+128后繞回從0開始繼續(xù)。
20.3 硬件設計
紅楓派開發(fā)板SPI——NAND FLASH的硬件設計如下:
從圖中可以看出,本實驗使用的是普通單線SPI,GD5F1GQ5UEYIGY的片選由GD32F303ZET6的PG13控制(因PG14不是SPI的NSS管腳,所以本實驗用主機NSS軟件模式,,通過普通IO控制片選),GD25Q32ESIGR的SO、SI和SCLK分別和GD32F303ZET6的PB4(SPI2_MISO)、PB5(SPI2_MOSI)以及PB3(SPI2_CLK)相連。
20.4 代碼解析
20.4.1 SPI初始化和讀寫B(tài)YTE函數(shù)實現(xiàn)
SPI初始化配置流程可參考19.4.1東方紅開發(fā)板使用手冊 ;
SPI讀寫B(tài)YTE函數(shù)實現(xiàn)可參考19.4.2東方紅開發(fā)板使用手冊 ;
20.4.2 SPI NAND FLASH BSP驅動層實現(xiàn)
操作NAND FLASH的函數(shù)都定義在bsp層文件bsp_spi_nand.c中,這個文件中定義的函數(shù)都是針對NAND FLASH命令來實現(xiàn)的,我們選取幾個函數(shù)進行介紹。
? NOR FLASH按block擦除函數(shù)bsp_nandflash_block_erase,輸入block號即可擦除;該函數(shù)流程是:使能NAND FLASH的寫功能->向NOR FLASH發(fā)送block擦除指令0xD8->發(fā)送左移6位的Block NO->查詢OIP標志等待完成
C
/*!
\brief ?????erase the nandflash blcok
\param[in] ?block_No:the serial number of erase block
\param[out] none
\retval ????SPI_NAND_FAIL: erase the nandflash block fail
\retval ????SPI_NAND_SUCCESS: erase the nandflash block success
*/
uint8_t bsp_spi_nandflash_block_erase(uint32_t block_No)
{
uint8_t result = SPI_NAND_SUCCESS;
block_No<<=6; ???????//block_No=block_No*64
bsp_spi_nandflash_write_enable();
/* select the flash: chip select low */
bsp_spi_nand_cs_low();
/* send "ERASE BLOCK" command */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,SPI_NAND_BLOCK_ERASE);
/* send the address of memory */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(block_No>>16)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(block_No>>8)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,block_No&0xFF);
/* deselect the flash: chip select high */
bsp_spi_nand_cs_high();
while(bsp_spi_nandflash_get_status_flag(OIP)==SPI_NAND_BUSY);
/* check program result */
return result;
}
? NOR FLASH按page寫入函數(shù)bsp_nandflash_page_program,輸入待寫入數(shù)據(jù)指針、block號、page號;該函數(shù)流程是:
? 寫緩沖區(qū),實現(xiàn)流程:向NOR FLASH發(fā)送寫緩沖區(qū)指令0x02->發(fā)送寫入的page偏移地址->發(fā)送待寫入數(shù)據(jù)
? 載入數(shù)據(jù)到page,實現(xiàn)流程:使能NAND FLASH的寫功能->發(fā)送載入命令0x10->發(fā)送寫入的page號
? 查詢OIP標志等待完成
C
/*!
\brief ?????send the program load command,write data to cache
\param[in] ?buffer: the data of array
\param[in] ?address_in_page: the address in nandflash page
\param[in] ?byte_cnt: the number of data
\param[out] none
\retval ????none
*/
void bsp_spi_nandflash_program_load(uint8_t *buffer,uint16_t address_in_page,uint32_t byte_cnt)
{
uint32_t i=0;
/* select the flash: chip select low */
bsp_spi_nand_cs_low();
/* send "PAGE READ" command */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,SPI_NAND_PAGE_LOAD);
/* send the serial number of page */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(address_in_page>>8)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,address_in_page&0xFF);
/* deselect the flash: chip select high */
for(i=0;i<byte_cnt;i++){
driver_spi_master_transmit_receive_byte(&BOARD_SPI,*buffer++);
}
//printf("cache program %x %x\n\r",m32record[0],m32record[1]);
bsp_spi_nand_cs_high();
qspi_disable(BOARD_SPI.spi_x);
}
/*!
\brief ?????send the program excute command
\param[in] ?page_No: the serial number of nandflash page
\param[out] none
\retval ????none
*/
void bsp_spi_nandflash_program_execute(uint32_t page_No)
{
/* enable the write access to the flash */
bsp_spi_nandflash_write_enable();
/* select the flash: chip select low */
bsp_spi_nand_cs_low();
/* send "PAGE READ" command */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,SPI_NAND_PROGRAM_EXEC);
/* send the serial number of page */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(page_No>>16)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(page_No>>8)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,page_No&0xFF);
/* deselect the flash: chip select high */
bsp_spi_nand_cs_high();
}
/*!
\brief ?????write the data to nandflash
\param[in] ?*buffer:the data of array
\param[in] ?page_No: the serial number of nandflash page
\param[in] ?address_in_page: the address of nandflash page
\param[in] ?byte_cnt:the number of data
\param[out] none
\retval ????SPI_NAND_FAIL,SPI_NAND_SUCCESS
*/
uint8_t spi_nandflash_write_data(uint8_t *buffer,uint32_t page_No,uint16_t address_page,uint32_t byte_cnt)
{
/*sned the program load command,write data to cache*/
bsp_spi_nandflash_program_load(buffer, address_page, byte_cnt);
/*sned the program excute command*/
bsp_spi_nandflash_program_execute(page_No);
/* Check program result */
while(bsp_spi_nandflash_get_status_flag(OIP)==SPI_NAND_BUSY);
spi_nandflash_read_data (tem_buffer,page_No, address_page, byte_cnt);
if (memcmp(tem_buffer, buffer, ?byte_cnt) != 0){
return SUCCESS;
}
return 1;
}
? NOR FLASH按page讀取函數(shù)spi_nandflash_read_data,輸入讀取數(shù)據(jù)指針、page號、page內(nèi)地址偏移、讀取長度;該函數(shù)流程是:
? 讀page到緩沖區(qū),實現(xiàn)流程:向NOR FLASH發(fā)送寫緩沖區(qū)指令0x13->送要讀取的page號
? 等待OIP標志(NAND讀取page到緩沖區(qū)完成)
? 從緩沖區(qū)讀取數(shù)據(jù),實現(xiàn)流程:發(fā)送讀cache命令0x03->發(fā)送要讀取的page地址偏移->讀取所需長度的數(shù)據(jù)
? 查詢是否有ecc錯誤
C
/*!
\brief ?????send the read page command
\param[in] ?page_No: the serial number of nandflash page
\param[out] none
\retval ????none
*/
void bsp_spi_nandflash_page_read(uint32_t page_No)
{
/* select the flash: chip select low */
bsp_spi_nand_cs_low();
/* send "PAGE READ" command */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,SPI_NAND_PAGE_READ);
/* send the serial number of page */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(page_No>>16)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(page_No>>8)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,page_No&0xFF);
/* deselect the flash: chip select high */
bsp_spi_nand_cs_high();
}
/*!
\brief ?????send the read cache command
\param[in] ?buffer: a pointer to the array
\param[in] ?address_in_page: the address in nandflash page
\param[in] ?byte_cnt: the number of data
\param[out] none
\retval ????none
*/
void bsp_spi_nandflash_read_cache(uint8_t *buffer,uint16_t address_in_page,uint32_t byte_cnt)
{
uint32_t i=0;
/* select the flash: chip select low */
bsp_spi_nand_cs_low();
/* send "PAGE READ" command */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,SPI_NAND_READ_CACHE);
//driver_spi_master_transmit_receive_byte(&BOARD_SPI,DUMMY_BYTE);//Q4UC ++ Q5 --
/* send the address of page */
driver_spi_master_transmit_receive_byte(&BOARD_SPI,(address_in_page>>8)&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,address_in_page&0xFF);
driver_spi_master_transmit_receive_byte(&BOARD_SPI,DUMMY_BYTE);//Q4UC -- Q5 ++
for(i=0;i<byte_cnt;i++){
*buffer++=driver_spi_master_transmit_receive_byte(&BOARD_SPI,DUMMY_BYTE);
}
/* deselect the flash: chip select high */
bsp_spi_nand_cs_high();
qspi_disable(BOARD_SPI.spi_x);
}
/*!
\brief ?????read the data from nandflash
\param[in] ?*buffer:the data of array
\param[in] ?page_No: the serial number of nandflash page
\param[in] ?address_in_page: the address in nandflash page
\param[in] ?byte_cnt:the number of data
\param[out] none
\retval ????SPI_NAND_FAIL,SPI_NAND_SUCCESS
*/
uint8_t spi_nandflash_read_data(uint8_t *buffer,uint32_t page_No,uint32_t address_in_page,uint32_t byte_cnt)
{
uint8_t result = SPI_NAND_SUCCESS;
uint8_t status = 0;
uint8_t retrycnt = 0;
/* the capacity of page must be equal or greater than the taotal of address_in_page and byte_cnt */
if((address_in_page+byte_cnt)>SPI_NAND_PAGE_TOTAL_SIZE){
return SPI_NAND_FAIL;
}
ReadRetry:
/* send the read page command */
bsp_spi_nandflash_page_read(page_No);
/* wait for NANDFLASH is ready */
while(bsp_spi_nandflash_get_status_flag(OIP)==SPI_NAND_BUSY);
/* read data from cache */
bsp_spi_nandflash_read_cache(buffer, address_in_page, byte_cnt);
bsp_spi_nandflash_get_feature( STATUS, &status );
if(( (status & ECCS0) == 0 )&&( (status & ECCS1) == ECCS1 )){ ???//UECC
if(retrycnt < 3)
{
retrycnt++;
printf("\rReadretry:%x %x\n",retrycnt,page_No);
goto ReadRetry;
}
else
{
printf("\rRead Fail %x\n",page_No);
}
}
return result;
}
20.4.3 main函數(shù)實現(xiàn)
main函數(shù)中實現(xiàn)了擦除一個block,并對該block中的page進行寫入操作,然后讀取后進行數(shù)據(jù)對比校驗的功能。
C
/*!
* 說明 ????main函數(shù)
* 輸入 ????無
* 輸出 ????無
* 返回值 ??無
*/
int main(void)
{
//延時、共用驅動部分初始化
driver_init();
//初始化LED組和默認狀態(tài)
bsp_led_group_init();
bsp_led_on(&LED0);
bsp_led_off(&LED1);
//初始化UART打印
bsp_uart_init(&BOARD_UART);
//初始化SPI
bsp_spi_init(&BOARD_SPI);
//初始化SPI NAND
bsp_spi_nand_init();
printf("\n\rSPI NAND:GD5F1G configured...\n\r");
//讀取flash id
flash_id=bsp_spi_nandflash_read_id();
printf("\n\rThe NAND_ID:0x%X\n\r",flash_id);
//比對flash id是否一致
if(NAND_ID != flash_id)
{
printf("\n\r\n\rWrite to tx_buffer:\n\r\n\r");
//準備數(shù)據(jù)
for(uint16_t i = 0; i < BUFFER_SIZE; i ++){
tx_buffer[i] = i;
printf("0x%02X ",tx_buffer[i]);
if(15 == i%16)
printf("\n\r");
}
printf("\n\r\n\rRead from rx_buffer:\n\r");
//擦除要寫入的block
bsp_nandflash_block_erase(0);
//寫入數(shù)據(jù)
bsp_nandflash_page_program((uint8_t*)tx_buffer,0,0,0);
//回讀寫入數(shù)據(jù)
bsp_nandflash_page_read(rx_buffer,0,0);
/* printf rx_buffer value */
for(uint16_t i = 0; i <= 255; i ++){
printf("0x%02X ", rx_buffer[i]);
if(15 == i%16)
printf("\n\r");
}
//比較回讀和寫入數(shù)據(jù)
if(ERROR == memory_compare(tx_buffer,rx_buffer,BUFFER_SIZE)){
printf("Err:Data Read and Write aren't Matching.\n\r");
/* spi flash read id fail */
printf("\n\rSPI nand: Read ID Fail!\n\r");
//寫入錯誤
/* turn off all leds */
bsp_led_on(&LED0);
/* turn off all leds */
bsp_led_on(&LED1);
while(1);
}else{
printf("\n\rSPI-GD5F1G Test Passed!\n\r");
}
}else{ //ID讀取錯誤
/* spi flash read id fail */
printf("\n\rSPI Nand:Read ID Fail!\n\r");
/* turn off all leds */
bsp_led_on(&LED0);
/* turn off all leds */
bsp_led_on(&LED1);
while(1);
}
while(1){
/* turn off all leds */
bsp_led_toggle(&LED0);
/* turn off all leds */
bsp_led_toggle(&LED1);
delay_ms(200);
}
}
20.5 實驗結果
nand讀取到正確ID后開始擦寫讀流程,如果ID讀取錯誤或者數(shù)據(jù)比對不通過點亮LED0,熄滅LED1,如果比對通過則交替閃爍LED0和LED1,通過USB轉串口可以看到打印結果。
紅楓派開發(fā)板使用手冊:??????????????????????????????????????????????????GD32F303紅楓派使用手冊 - 飛書云文檔 (feishu.cn)