/****************************************************************************** 版权所有: 文件名称: flash_at45db321.c 文件版本: 01.01 创建作者: sunxi 创建日期: 2008-08-19 功能说明: at45db321驱动程序。 其它说明: 目前通过页擦除整个FLASH需要64.8秒,读整个FLASH需要6.3秒,写整个FLASH 需要121秒(首版测试结果)。 修改记录: 2010-07-16 sunxi _at45db321_page_write函数以前是由OP_PROGRAM_VIA_BUF1命令 完成,现改为由OP_WRITE_BUFFER1和OP_MWRITE_BUFFER1命令组合 完成。这样做的原因OP_PROGRAM_VIA_BUF1实际由BUF写、页擦除、 页编程三个命令组合而成,由于有页擦除命令的存在,导致页写的 时间速度比较慢,要15ms左右,改进后只需3.5ms左右。使用本修改 后的程序必须确保写之前已经对相应的页进行了擦除。 新版本代码的测试结果如下: flash_test begin! block_size=512,blocks=8192. at45db321d_Identify ok!(us=9)! at45db321_sector_protection:ret=0! at45DB321Erase (us=77945334,ret=0)! at45DB321Erase verify ok(us=6230836)! at45DB321Write ok(us=28132804)! at45DB321Write verify ok(us=6217728)! at45DB321Erase (us=69492882,ret=0)! at45DB321Erase verify ok(us=6243943)! at45DB321Write ok(us=28350756)! at45DB321Write verify ok(us=6322586)! flash_test end! */ /*------------------------------- 头文件 -------------------------------------- */ #include #include "bspconfig.h" #include "ustimer.h" #include "dspi.h" #include "flash.h" #include "gpio.h" #include "rt.h" /*------------------------------- 宏定义 -------------------------------------- */ #define _DEBUG_FLASH #if CFG_BSP_DEBUG #define _DEBUG_FLASH #endif //操作命令宏定义 #define OP_READ_CONTINUOUS 0xE8 #define OP_READ_PAGE 0xD2 #define OP_READ_STATUS 0xD7 #define OP_READ_BUFFER1 0xD4 #define OP_READ_BUFFER2 0xD6 #define OP_WRITE_BUFFER1 0x84 #define OP_WRITE_BUFFER2 0x87 /* erasing flash */ #define OP_ERASE_PAGE 0x81 #define OP_ERASE_BLOCK 0x50 /* move data between buffer and flash */ #define OP_TRANSFER_BUF1 0x53 #define OP_TRANSFER_BUF2 0x55 #define OP_MREAD_BUFFER1 0xD4 #define OP_MREAD_BUFFER2 0xD6 #define OP_MWERASE_BUFFER1 0x83 #define OP_MWERASE_BUFFER2 0x86 #define OP_MWRITE_BUFFER1 0x88 #define OP_MWRITE_BUFFER2 0x89 /* write to buffer, then write-erase to flash */ #define OP_PROGRAM_VIA_BUF1 0x82 #define OP_PROGRAM_VIA_BUF2 0x85 /* compare buffer to flash */ #define OP_COMPARE_BUF1 0x60 #define OP_COMPARE_BUF2 0x61 /* read flash to buffer, then write-erase to flash */ #define OP_REWRITE_VIA_BUF1 0x58 #define OP_REWRITE_VIA_BUF2 0x59 /* newer chips report JEDEC manufacturer and device IDs; chip * serial number and OTP bits; and per-sector writeprotect. */ #define OP_READ_ID 0x9F #define OP_READ_SECURITY 0x77 #define OP_WRITE_SECURITY 0x9A /*------------------------------ 全局变量 ------------------------------------- */ int g_flash_is_ok; /*------------------------------ 函数声明 ------------------------------------- */ int _at45db321_identify(void); int _at45db321_page_read(uint32_t offset,unsigned char * buffer,uint32_t length); int _at45db321_page_write(uint32_t offset,unsigned char * buffer,uint32_t length); int _at45db321_page_erase(uint32_t offset); int _at45db321_wait_ready(unsigned char *p_status_register); /*------------------------------ 外部函数 ------------------------------------- */ /****************************************************************************** 函数名称: flash_init 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-19 函数说明: flash初始化。 参数说明: 无 返回值: 成功返回0. 修改记录: */ int flash_init(void) { // GPIO_WP_FLASH_INIT(); if(_at45db321_identify() == 0) { g_flash_is_ok = 1; } else { g_flash_is_ok = 0; } return 0; } int flash_exit(void) { return 0; } /****************************************************************************** 函数名称: flash_read 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 从FLASH中读出数据。 参数说明: offset(in):需要读的数据在FLASH中的偏移量,和length之和不能大于 CFG_FLASH_SIZE. buffer(out):读出数据的buffer。 length(in): 需要读出数据的长度,和offset之和不能大于CFG_FLASH_SIZE。 返回值: 成功返回0. 修改记录: */ int flash_read(uint32_t offset,unsigned char * buffer,uint32_t length) { int ret; uint32_t length_once,length_spare; //检查FLASH是否OK if(g_flash_is_ok == 0) { return -1; } //检查参数 if(buffer == 0 || (offset + length) > CFG_FLASH_SIZE) { return -1; } if(length == 0) { return 0; } //初始化length_spare length_spare = length; //读起始不完整页 length_once = CFG_FLASH_PAGE_SIZE - offset%CFG_FLASH_PAGE_SIZE; if(length_once > length_spare) { length_once = length_spare; } if(length_once) { ret = _at45db321_page_read(offset,buffer,length_once); if(ret != 0) { return ret; } offset += length_once; buffer += length_once; length_spare -= length_once; } //循环读完整页。 length_once = CFG_FLASH_PAGE_SIZE; while(length_spare > CFG_FLASH_PAGE_SIZE) { ret = _at45db321_page_read(offset,buffer,length_once); if(ret != 0) { return ret; } offset += length_once; buffer += length_once; length_spare -= length_once; } //读末尾不完整页。 length_once = length_spare; if(length_once) { ret = _at45db321_page_read(offset,buffer,length_once); if(ret != 0) { return ret; } } return 0; } /****************************************************************************** 函数名称: flash_write 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 将数据写入FLASH中。 参数说明: offset(in):需要写入的数据在FLASH中的偏移量,和length之和不能大于 CFG_FLASH_SIZE. buffer(in):写入数据的buffer。 length(in): 需要写入数据的长度,和offset之和不能大于CFG_FLASH_SIZE。 返回值: 成功返回0. 修改记录: */ int flash_write(uint32_t offset,unsigned char * buffer,uint32_t length) { int ret; uint32_t length_once,length_spare; //检查FLASH是否OK if(g_flash_is_ok == 0) { return -1; } //检查参数 if(buffer == 0 || (offset + length) > CFG_FLASH_SIZE) { return -1; } if(length == 0) { return 0; } //初始化length_spare length_spare = length; //写起始不完整页 length_once = CFG_FLASH_PAGE_SIZE - offset%CFG_FLASH_PAGE_SIZE; if(length_once > length_spare) { length_once = length_spare; } if(length_once) { ret = _at45db321_page_write(offset,buffer,length_once); if(ret != 0) { return ret; } offset += length_once; buffer += length_once; length_spare -= length_once; } //循环写完整页。 length_once = CFG_FLASH_PAGE_SIZE; while(length_spare > CFG_FLASH_PAGE_SIZE) { ret = _at45db321_page_write(offset,buffer,length_once); if(ret != 0) { return ret; } offset += length_once; buffer += length_once; length_spare -= length_once; } //写末尾不完整页。 length_once = length_spare; if(length_once) { ret = _at45db321_page_write(offset,buffer,length_once); if(ret != 0) { return ret; } } return 0; } /****************************************************************************** 函数名称: flash_erase 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 擦除FLASH中的指定区域。注意擦除是以页(CFG_FLASH_PAGE_SIZE)为单位 擦除的,为了保证用户指定的区域一定被擦除,实际擦除的长度可能大于指定 的长度,用户应小心处理这个问题。 参数说明: offset(in):需要擦除的页在FLASH中的偏移量,和length之和不能大于 CFG_FLASH_SIZE. length(in): 需要擦除的长度,和offset之和不能大于CFG_FLASH_SIZE。 返回值: 成功返回0. 修改记录: */ int flash_erase(uint32_t offset,uint32_t length) { uint32_t offset_last; //检查FLASH是否OK if(g_flash_is_ok == 0) { return -1; } //检查参数 if((offset + length) > CFG_FLASH_SIZE) { return -1; } if(length == 0) { return 0; } //循环擦除指定空间 offset_last = offset + length; offset -= offset%CFG_FLASH_PAGE_SIZE; while(offset < offset_last) { if(_at45db321_page_erase(offset) != 0) { return -2; } offset += CFG_FLASH_PAGE_SIZE; } return 0; } /****************************************************************************** 函数名称: at45db321_sector_protection 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-10-10 函数说明: 启用FLASH的扇区保护功能。 参数说明: 无。 返回值: 成功返回0. 修改记录: */ int at45db321_sector_protection(void) { int h_qspi,i,ret; unsigned char cmd[4]; //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -1; } //解除写保护 // GPIO_WP_FLASH_HIGH(); ustimer_delay(1*USTIMER_US); //写命令和地址 //擦除扇区保护寄存器命令(0x3d,0x2a,0x7f,0xcf) cmd[0] = 0x3d; cmd[1] = 0x2a; cmd[2] = 0x7f; cmd[3] = 0xcf; dspi_write(h_qspi,cmd,4); //关闭总线 dspi_close(h_qspi); //等待flash操作完成 ret = _at45db321_wait_ready(0); //写保护 // GPIO_WP_FLASH_LOW(); //开始确认保护是否完整 //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -1; } //写命令和地址 //读扇区保护寄存器命令(0x32,0xxx,0xxx,0xxx) cmd[0] = 0x32; cmd[1] = 0x0; cmd[2] = 0x0; cmd[3] = 0x0; dspi_write(h_qspi,cmd,4); //读出数据比较 for(i=0; i<64; i++) { dspi_read(h_qspi,cmd,1); if(cmd[0] != 0xff) { ret = -2; } } //关闭总线 dspi_close(h_qspi); if(ret != 0) { return -2; } return 0; } /*------------------------------ 内部函数 ------------------------------------- */ /****************************************************************************** 函数名称: _at45db321_wait_ready 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 等待FLASH完成操作,如果需要返回状态寄存器的值 参数说明: p_status_register(out):如果不为0,返回状态寄存器的值 返回值: 成功返回0. 修改记录: */ int _at45db321_wait_ready(unsigned char *p_status_register) { int h_qspi,ret; uint32_t us0,us; unsigned char c; //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -1; } //写命令 c = OP_READ_STATUS; dspi_write(h_qspi,&c,1); //超时等待就绪 us0= ustimer_get_origin(); while(1) { // 得到超时值 us = ustimer_get_duration(us0); //读状态寄存器 dspi_read(h_qspi,&c,1); if(c & 0x80) { ret = 0; break; } //最大BLOCK擦除时间为100ms if(us > 100*USTIMER_MS) { ret = -1; break; } } //需要的话,返回状态寄存器的值 if(ret == 0 && p_status_register != 0) { dspi_read(h_qspi,p_status_register,1); } //关闭总线 dspi_close(h_qspi); return ret; } /****************************************************************************** 函数名称: _at45db321_identify 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 检查AT45DB321是否OK 参数说明: 无 返回值: 成功返回0. 修改记录: */ int _at45db321_identify(void) { int h_qspi,ret; unsigned char buffer[2]; // unsigned char dev_info[2] = {0x1F, 0x27}; unsigned char dev_info[2] = {0x20, 0xba}; //n25q h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -1; } buffer[0] = OP_READ_ID; dspi_write(h_qspi,buffer,1); dspi_read(h_qspi,buffer,2); rt_printf("FLASH:ID0=%02x,ID1=%02x.\r\n",buffer[0],buffer[1]); if(memcmp(buffer,dev_info,2) != 0) { ret = -2; } else { ret = 0; } dspi_close(h_qspi); return ret; } /****************************************************************************** 函数名称: _at45db321_page_read 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 从FLASH的一页中读出数据。 参数说明: offset(in):需要读的数据在FLASH中的偏移量。 buffer(out):读出数据的buffer。 length(in): 需要读出数据的长度。 返回值: 成功返回0. 修改记录: */ int _at45db321_page_read(uint32_t offset,unsigned char * buffer,uint32_t length) { int h_qspi; uint32_t pa,ba; unsigned char cmd[4]; //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -1; } //得到页地址和页内偏移 pa = offset/CFG_FLASH_PAGE_SIZE; ba = offset%CFG_FLASH_PAGE_SIZE; //1 reserved bit + 13位的页面地址 + 10位的页内地址 //R P12 P11 P10 P9 P8 P7 P6 | P5 P4 P3 P2 P1 P0 B9 B8 | B7 B6 B5 B4 B3 B2 B1 B0 cmd[1] = (unsigned char)(pa>>6); cmd[2] = (unsigned char)(unsigned char)(pa<<2 | (ba>>8 & 0x3)); cmd[3] = (unsigned char)ba; //写命令和地址 cmd[0] = OP_READ_PAGE; dspi_write(h_qspi,cmd,4); //等待32个时钟周期 dspi_dummy_byte(h_qspi,4); //读出数据 dspi_read(h_qspi,buffer,length); //关闭总线 dspi_close(h_qspi); return 0; } /****************************************************************************** 函数名称: _at45db321_page_write 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 将数据写入FLASH的一页中。 参数说明: offset(in):需要写入的数据在FLASH中的偏移量. buffer(in):写入数据的buffer。 length(in): 需要写入数据的长度。 返回值: 成功返回0. 修改记录: 2010-07-16 sunxi _at45db321_page_write函数以前是由OP_PROGRAM_VIA_BUF1命令 完成,现改为由OP_WRITE_BUFFER1和OP_MWRITE_BUFFER1命令组合 完成。见文件头说明 */ int _at45db321_page_write(uint32_t offset,unsigned char * buffer,uint32_t length) { int h_qspi,ret; uint32_t pa,ba; unsigned char cmd[4],status; //得到页地址和页内偏移 pa = offset/CFG_FLASH_PAGE_SIZE; ba = offset%CFG_FLASH_PAGE_SIZE; //1 reserved bit + 13位的页面地址 + 10位的页内地址 //R P12 P11 P10 P9 P8 P7 P6 | P5 P4 P3 P2 P1 P0 B9 B8 | B7 B6 B5 B4 B3 B2 B1 B0 cmd[1] = (unsigned char)(pa>>6); cmd[2] = (unsigned char)(unsigned char)(pa<<2 | (ba>>8 & 0x3)); cmd[3] = (unsigned char)ba; //1.先将数据从FLASH中读出到BUFFER1 //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -1; } //解除写保护 // GPIO_WP_FLASH_HIGH(); ustimer_delay(USTIMER_US); //写命令和地址 cmd[0] = OP_TRANSFER_BUF1; dspi_write(h_qspi,cmd,4); //关闭总线 dspi_close(h_qspi); //等待flash操作完成 if(_at45db321_wait_ready(0) != 0) { return -2; } //2. 将数据写入BUF //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -3; } //写命令和地址 cmd[0] = OP_WRITE_BUFFER1; dspi_write(h_qspi,cmd,4); //写入数据 dspi_write(h_qspi,buffer,length); //关闭总线 dspi_close(h_qspi); //3. 将数据从BUF写入FLASH //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -33; } //写命令和地址 cmd[0] = OP_MWRITE_BUFFER1; dspi_write(h_qspi,cmd,4); //关闭总线 dspi_close(h_qspi); //等待flash操作完成 ret = _at45db321_wait_ready(0); //写保护 // GPIO_WP_FLASH_LOW(); if(ret != 0) { return -4; } //4. 检查写入的数据是否正确 //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -5; } //写命令和地址 cmd[0] = OP_COMPARE_BUF1; dspi_write(h_qspi,cmd,4); //关闭总线 dspi_close(h_qspi); //等待flash操作完成 if(_at45db321_wait_ready(&status) != 0) { return -6; } //比较不正确的话,返回出错。 if(status & 0x40) { return -7; } return 0; } /****************************************************************************** 函数名称: _at45db321_page_erase 函数版本: 01.01 创建作者: sunxi 创建日期: 2008-08-21 函数说明: 擦除FLASH中的一页。 参数说明: offset(in):需要擦除的页在FLASH中的偏移量. 返回值: 成功返回0. 修改记录: */ int _at45db321_page_erase(uint32_t offset) { int h_qspi,ret; uint32_t pa,ba; unsigned char cmd[4]; //获取总线 h_qspi = dspi_open(DSPI_ID_FLASH); if(h_qspi < 0) { return -1; } //解除写保护 // GPIO_WP_FLASH_HIGH(); ustimer_delay(USTIMER_US); //得到页地址和页内偏移 pa = offset/CFG_FLASH_PAGE_SIZE; ba = offset%CFG_FLASH_PAGE_SIZE; //1 reserved bit + 13位的页面地址 + 10位的页内地址 //R P12 P11 P10 P9 P8 P7 P6 | P5 P4 P3 P2 P1 P0 B9 B8 | B7 B6 B5 B4 B3 B2 B1 B0 cmd[1] = (unsigned char)(pa>>6); cmd[2] = (unsigned char)(unsigned char)(pa<<2 | (ba>>8 & 0x3)); cmd[3] = (unsigned char)ba; //写命令和地址 cmd[0] = OP_ERASE_PAGE; dspi_write(h_qspi,cmd,4); //关闭总线 dspi_close(h_qspi); //等待flash操作完成 ret = _at45db321_wait_ready(0); //写保护 // GPIO_WP_FLASH_LOW(); if(ret != 0) { return -2; } return 0; } /*------------------------------ 测试函数 ------------------------------------- */ #ifdef _DEBUG_FLASH #define FLASH_BLOCK_SIZE CFG_FLASH_PAGE_SIZE int flash_test(void) { int ret; u32 i, j; uint32_t block_size,blocks; uint32_t us0,us; unsigned int gTestBuf[FLASH_BLOCK_SIZE/sizeof(int)]; block_size = FLASH_BLOCK_SIZE; blocks = CFG_FLASH_SIZE/FLASH_BLOCK_SIZE; // blocks = 1; rt_printf("flash_test begin!\r\n"); rt_printf("block_size=%lu,blocks=%lu.\r\n",block_size,blocks); // dspi_init(); us0 = ustimer_get_origin(); //while(1) ret = _at45db321_identify(); us = ustimer_get_duration(us0); if(ret != 0) { rt_printf("at45db321d_Identify error!(ret=%d)!\r\n",ret); return -1; } rt_printf("at45db321d_Identify ok!(us=%lu)!\r\n",us); //启用FLASH的扇区保护功能 ret = at45db321_sector_protection(); rt_printf("at45db321_sector_protection:ret=%d!\r\n",ret); //擦除整个flash。 us0 = ustimer_get_origin(); ret = flash_erase(0,blocks*block_size); us = ustimer_get_duration(us0); rt_printf("at45DB321Erase (us=%lu,ret=%d)!\r\n",us,ret); //确认擦除OK us0 = ustimer_get_origin(); for(i=0;i