单片机
返回首页

【GD32F303红枫派开发板使用手册】第二十讲 SPI-SPI NAND FLASH读写实验

2024-11-18 来源:elecfans

20.1实验内容

通过本实验主要学习以下内容:

  • SPI通信协议,参考19.2.1东方红开发板使用手册

  • GD32F303 SPI操作方式,参考19.2.2东方红开发板使用手册

  • NAND FLASH基本原理

  • SPI NAND介绍

  • 使用GD32F303 SPI接口实现对GD5F1GQ5UEYIGY的读写操作

20.2实验原理

20.2.1NAND FLASH基本原理

NAND Flash和NOR Flash都是两种非易失性存储器,其读写速度、读写方式,存储区结构、成本、容量、擦写寿命都有很大区别。NAND在寿命、速度、读写方式上都不如NOR,但在成本和容量上有很大区别,故而决定了大容量数据存储是NAND的主要应用领域,而快速启动、快速数据读取等场景是NOR的主要应用领域。而SPI是目前NAND和NOR的主要通信接口形式,降低了器件体积,标准化了器件接口。

  • NAND Flash结构示例

wKgaomZzie-ASHMCAACpYmz1joQ526.png

如上图所示,以GD5F1GQ5UEYIGY为例,一个1Gb的存储结构下是由1024个block组成,每个block又64page组成,每个page是2K Main Area+Spare Area(ECC ON:64B;ECC OFF:128B)组成。

NAND的擦除单位是blocks,写入单位是page,所以寻址的方式上和nor是有本质区别的,需要按blocks、page、page字节偏移地址进行一个数据的寻址。

20.2.2SPI NAND介绍

SPI NAND简化了NAND的接口设计和尺寸,SPI接口更是降低了主控对接口的要求,同时内置ECC。下图是GD5F1GQ5UEYIGY的命令表,常用的命令为擦除、编程、读取命令。

wKgZomZzifqAdlMUAAKpHZv_HdQ326.png

  • block擦除命令

wKgZomZzigeAKIIUAAES6EvOPGw289.png

  • 编程

  • 编程流程

  1. 先用数据缓存写入指令将数据写入缓冲区

  2. 然后发送写使能命令,并确认写使能成功

  3. 然后发送数据载入命令执行缓冲区数据到FLASH的写

  4. 最后查询读寄存器确认P_FAIL是否有错,OIP是否完成

注意(84h/C4h/34h) 和(FFh)指令是不会清除缓存中的内容的,所以下次编程时要注意是否缓存区都是需要更新的数据,所以必须是一次更新整个缓冲区,不要部分更新。

编程page地址按照块的顺序

  • 数据缓存写入命令

wKgZomZzihqAQqKoAADV6NvTRjk583.png

  • 数据载入命令

wKgZomZzij2AZf6eAAEgT0-fPeU146.png

  • 读取

  • 读取流程

  1. 读需要先通过读cache命令从FLASH中读出数据到缓存中

  2. 然后通过读cache指令从缓冲区中开始读出数据

读到2048+128后绕回从0开始继续。

20.3硬件设计

红枫派开发板SPI——NAND FLASH的硬件设计如下:

wKgZomZzikyAAxbIAADuwlzsORg877.pngwKgZomZyPjeALX0BAADP0lLHKYs115.png

从图中可以看出,本实验使用的是普通单线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.1SPI初始化和读写BYTE函数实现

SPI初始化配置流程可参考19.4.1东方红开发板使用手册;

SPI读写BYTE函数实现可参考19.4.2东方红开发板使用手册;

20.4.2SPI NAND FLASH BSP驱动层实现

操作NAND FLASH的函数都定义在bsp层文件bsp_spi_nand.c中,这个文件中定义的函数都是针对NAND FLASH命令来实现的,我们选取几个函数进行介绍。

  • NOR FLASH按block擦除函数bsp_nandflash_block_erase,输入block号即可擦除;该函数流程是:使能NAND FLASH的写功能->向NOR FLASH发送block擦除指令0xD8->发送左移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写入函数bsp_nandflash_page_program,输入待写入数据指针、block号、page号;该函数流程是:

写缓冲区,实现流程:向NOR FLASH发送写缓冲区指令0x02->发送写入的page偏移地址->发送待写入数据

载入数据到page,实现流程:使能NAND FLASH的写功能->发送载入命令0x10->发送写入的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();

#ifdef SPI_NANDFLASH

/* 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);

#endif



/* deselect the flash: chip select high */



for(i=0;i driver_spi_master_transmit_receive_byte(&BOARD_SPI,*buffer++);

}

//printf('cache program %x %xnr',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);



#ifdef WRITE_PAGE_VERIFY_EN

spi_nandflash_read_data (tem_buffer,page_No, address_page, byte_cnt);

if (memcmp(tem_buffer, buffer, byte_cnt) != 0){

return SUCCESS;

}

#endif

return 1;


}

NOR FLASH按page读取函数spi_nandflash_read_data,输入读取数据指针、page号、page内地址偏移、读取长度;该函数流程是:

读page到缓冲区,实现流程:向NOR FLASH发送写缓冲区指令0x13->送要读取的page号

等待OIP标志(NAND读取page到缓冲区完成)

从缓冲区读取数据,实现流程:发送读cache命令0x03->发送要读取的page地址偏移->读取所需长度的数据

查询是否有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();

#ifdef SPI_NANDFLASH

/* 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 ++


#endif




for(i=0;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 %xn',retrycnt,page_No);


goto ReadRetry;

}

else

{

printf('rRead Fail %xn',page_No);

}

}

return result;

}

20.4.3main函数实现


main函数中实现了擦除一个block,并对该block中的page进行写入操作,然后读取后进行数据对比校验的功能。


C

/*!

* 说明 main函数

* 输入 无

* 输出 无

* 返回值 无

*/

int main(void)

{


//延时、共用驱动部分初始化

driver_init();


//初始化LED组和默认状态

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('nrSPI NAND:GD5F1G configured...nr');


//读取flash id

flash_id=bsp_spi_nandflash_read_id();

printf('nrThe NAND_ID:0x%Xnr',flash_id);


//比对flash id是否一致

if(NAND_ID != flash_id)

{

printf('nrnrWrite to tx_buffer:nrnr');


//准备数据

for(uint16_t i = 0; i < BUFFER_SIZE; i ++){

tx_buffer[i] = i;

printf('0x%02X ',tx_buffer[i]);


if(15 == i%16)

printf('nr');

}


printf('nrnrRead from rx_buffer:nr');


//擦除要写入的block

bsp_nandflash_block_erase(0);

//写入数据

bsp_nandflash_page_program((uint8_t*)tx_buffer,0,0,0);


//回读写入数据

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('nr');

}


//比较回读和写入数据

if(ERROR == memory_compare(tx_buffer,rx_buffer,BUFFER_SIZE)){

printf('Err:Data Read and Write aren't Matching.nr');

/* spi flash read id fail */

printf('nrSPI nand: Read ID Fail!nr');


//写入错误

/* turn off all leds */

bsp_led_on(&LED0);

/* turn off all leds */

bsp_led_on(&LED1);

while(1);

}else{

printf('nrSPI-GD5F1G Test Passed!nr');

}

}else{ //ID读取错误

/* spi flash read id fail */

printf('nrSPI Nand:Read ID Fail!nr');

/* 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读取错误或者数据比对不通过点亮LED0,熄灭LED1,如果比对通过则交替闪烁LED0和LED1,通过USB转串口可以看到打印结果。

wKgaomZzim6ARlMMAAG4cbJ1vl0550.png

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 单稳态控制电路设计与分析

  • 永不缺相启动运行的电动机控制电路

  • MT3608构成3.7V转12V的升压电路图

  • 比较常见的功率整流器和滤波电路

  • 基于CA3193的热电偶放大器电路

  • 基于TDA1554的立体声放大器电路

    相关电子头条文章