SFUD--SFUD 全称 Serial Flash Universal Driver,是一款开源的串行 SPI Flash 通用驱动库,目前已经成为RT-Thread的一个软件包,作者正是RT-Thread的技术总监——armink,github地址为:https://github.com/armink/SFUD,关于SFUD的介绍可以在这个仓库上了解。关于SFUD的裸机移植,则可以参考这篇文章:https://blog.csdn.net/u012308586/article/details/105096969。
以下内容实际上是我大四的时候所写,水平非常有限 ,难免有理解错误的地方,望读者不要太计较。下面涉及的程序都是源码,如果嫌太长可以直接略过,只做大致了解即可。
目录
1. FLASH初始化配置
首先需要了解一个概念叫SFDP,支持SFDP标准的Flash芯片会Flash的参数放到一个特定区域内,这些参数包括芯片容量、擦写粒度、擦除命令、地址模式等 。
因此,SFUD的核心就在于通过读取SFDP来获取Flash的具体信息,这些信息包括了Flash的容量、擦写粒度、擦写命令等,由此来达到万能通用的效果。读取SFDP参数是先发送0x5A命令,然后再发送3字节地址来读取相应的SFDP参数。例如图1是W25Q128数据手册中关于0x5A命令的描述,图2则是相关的时序图。想知道自己的FLASH是否支持,在数据手册里搜0x5A即可,一般支持该命令的Flash,都可以用SFUD库来驱动。
SFUD在初始化时会优先读取 SFDP 表参数,如果该 Flash 不支持 SFDP,则查询配置文件 sfud_flash_def.h中提供的 Flash 参数信息表中是否支持该款 Flash。这个表相当于是用户代替了SFDP将Flash参数手动传给SFUD库,从而也能达到让SFUD支持该Flash的效果。
宏SFUD_USING_SFDP和SFUD_USING_FLASH_INFO_TABLE就是用来配置SFUD是通过读取SFDP来支持还是通过用户配置表进行手动配置,这两个宏也可以同时打开。同时打开的情况下SFUD会优先尝试读取SFDP的方式,如果这种方式失败了才会使用用户传入参数表的方式。如果确认自己的Flash支持SFDP,那么SFUD_USING_FLASH_INFO_TABLE可以不打开从而减小程序大小。
用户需要事先在sfud_cfg.h中定义FLASH设备的序号和相应的SPI序号。SFUD_USING_SFDP和SFUD_USING_FLASH_INFO_TABLE是两个宏定义开关,程序根据这两个宏定义进行条件编译。打开SFUD_USING_SFDP宏定义后,SFUD会对支持SFDP的标准FLASH进行初始化;打开SFUD_USING_FLASH_INFO_TABLE宏定义后,SFUD会根据用户在sfud_flash_def中SFUD_FLASH_CHIP_TABLE中定义的FLASH参数表来完成对FLASH的初始化,该表的参数包含了FLASH芯片名称、制造商ID、类型ID、容量ID、容量、写入模式、擦除粒度、擦除命令。也可以两个宏定义都打开,但是优先支持SFDP。
支持SFDP的FLASH设备会把'S'、'F'、'D'、'P'这四个字符以及SFDP的版本号按顺序放在FLASH的某8个字节空间里,发送0x5A(SFUD_CMD_READ_SFDP_REGISTER)命令后再发送0x00(起始地址,需要24字节对齐)便可以读出header,通过调用read_sfdp_data函数来完成,其作用就是读取指定地址里存放的SFDP参数。SFUD库已经把读取SFDP_header封装成read_sfdp_header,这两个函数可见下图的程序。
/**
* Read SFDP parameter header
*
* @param flash flash device
*
* @return true: read OK
*/
static bool read_sfdp_header(sfud_flash *flash) {
sfud_sfdp *sfdp = &flash->sfdp;
/* The SFDP header is located at address 000000h of the SFDP data structure.
* It identifies the SFDP Signature, the number of parameter headers, and the SFDP revision numbers. */
/* sfdp parameter header address */
uint32_t header_addr = 0;
/* each parameter header being 2 DWORDs (64-bit) */
uint8_t header[2 * 4] = { 0 };
SFUD_ASSERT(flash);
sfdp->available = false;
/* read SFDP header */
if (read_sfdp_data(flash, header_addr, header, sizeof(header)) != SFUD_SUCCESS) {
SFUD_INFO("Error: Can't read SFDP header.");
return false;
}
/* check SFDP header */
if (!(header[0] == 'S' &&
header[1] == 'F' &&
header[2] == 'D' &&
header[3] == 'P')) {
SFUD_DEBUG("Error: Check SFDP signature error. It's must be 50444653h('S' 'F' 'D' 'P').");
return false;
}
sfdp->minor_rev = header[4];
sfdp->major_rev = header[5];
if (sfdp->major_rev > SUPPORT_MAX_SFDP_MAJOR_REV) {
SFUD_INFO("Error: This reversion(V%d.%d) SFDP is not supported.", sfdp->major_rev, sfdp->minor_rev);
return false;
}
SFUD_DEBUG("Check SFDP header is OK. The reversion is V%d.%d, NPN is %d.", sfdp->major_rev, sfdp->minor_rev,
header[6]);
return true;
}
static sfud_err read_sfdp_data(const sfud_flash *flash, uint32_t addr, uint8_t *read_buf, size_t size) {
uint8_t cmd[] = {
SFUD_CMD_READ_SFDP_REGISTER,
(addr >> 16) & 0xFF,
(addr >> 8) & 0xFF,
(addr >> 0) & 0xFF,
SFUD_DUMMY_DATA,
};
SFUD_ASSERT(flash);
SFUD_ASSERT(addr < 1L << 24);
SFUD_ASSERT(read_buf);
SFUD_ASSERT(flash->spi.wr);
return flash->spi.wr(&flash->spi, cmd, sizeof(cmd), read_buf, size);
}
读出了SFDP的header还是不够的,还要读取basic_header。读取方式与读取header相似,只不过basic_header存放在起始地址为0x08的FLASH空间里,长度为8个字节。下面是read_basic_header函数。
/**
* Read JEDEC basic parameter header
*
* @param flash flash device
*
* @return true: read OK
*/
static bool read_basic_header(const sfud_flash *flash, sfdp_para_header *basic_header) {
/* The basic parameter header is mandatory, is defined by this standard, and starts at byte offset 08h. */
uint32_t header_addr = 8;
/* each parameter header being 2 DWORDs (64-bit) */
uint8_t header[2 * 4] = { 0 };
SFUD_ASSERT(flash);
SFUD_ASSERT(basic_header);
/* read JEDEC basic flash parameter header */
if (read_sfdp_data(flash, header_addr, header, sizeof(header)) != SFUD_SUCCESS) {
SFUD_INFO("Error: Can't read JEDEC basic flash parameter header.");
return false;
}
basic_header->id = header[0];
basic_header->minor_rev = header[1];
basic_header->major_rev = header[2];
basic_header->len = header[3];
basic_header->ptp = (long)header[4] | (long)header[5] << 8 | (long)header[6] << 16;
/* check JEDEC basic flash parameter header */
if (basic_header->major_rev > SUPPORT_MAX_SFDP_MAJOR_REV) {
SFUD_INFO("Error: This reversion(V%d.%d) JEDEC basic flash parameter header is not supported.",
basic_header->major_rev, basic_header->minor_rev);
return false;
}
if (basic_header->len < BASIC_TABLE_LEN) {
SFUD_INFO("Error: The JEDEC basic flash parameter table length (now is %d) error.", basic_header->len);
return false;
}
SFUD_DEBUG("Check JEDEC basic flash parameter header is OK. The table id is %d, reversion is V%d.%d,"
" length is %d, parameter table pointer is 0x%06lX.", basic_header->id, basic_header->major_rev,
basic_header->minor_rev, basic_header->len, basic_header->ptp);
return true;
}
最后,通过read_sfdp_data函数,读取起始地址为basic_header->ptp(通过read_basic_header读出)的SFDP参数,就可以返回FLASH设备的一系列参数,包括是否支持4KB字节擦除命令、写入模式(单字节还是256字节)、3字节还是4字节地址对齐方式、FLASH容量、擦除粒度及擦除命令等。SFUD已将此功能封装成了read_basic_table函数。
2. SFUD初始化
调用sfud_init函数完成FLASH的初始化,该函数遍历flash_table中定义的FLASH设备,即sfud_device_init(&flash_table[i]),依次调用hardware_init(flash)、software_init(flash)。
2.1 hardware_init
此函数是SFUD初始化中最重要的函数。
2.1.1 SPI初始化
sfud_port.c是用户配置文件,移植时需要修改此文件。首先按照flash_table中定义的spi号完成SPI的配置即sfud_spi_port_init,包括时钟、GPIO、SPI工作模式,这些都要根据自己的实际情况在回调函数里配置。注意要在sfud_spi_port_init函数定义的上一句spi_user_data spi1中告诉SFUD片选信号是哪个引脚,如下面的代码所示,片选信号的配置是在sfud_spi_port_init中对GPIO的配置中完成的。sfud_spi结构体中有一个user_data指针,用来指向用户数据,SFUD Demo中就将该成员指向了spi_user_data,其中包含SPI句柄、CS引脚等信息。
typedef struct {
SPI_HandleTypeDef *spix;
GPIO_TypeDef *cs_gpiox;
uint16_t cs_gpio_pin;
} spi_user_data, *spi_user_data_t;
static void spi_lock(const sfud_spi *spi) {
//__disable_irq();
}
static void spi_unlock(const sfud_spi *spi) {
// __enable_irq();
}
/**
* SPI write data then read data
*/
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
size_t read_size) {
sfud_err result = SFUD_SUCCESS;
uint8_t send_data, read_data;
spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_RESET);
for (size_t i = 0; i < write_size + read_size; i++) {
if (i < write_size) {
send_data = *write_buf++;
} else {
send_data = SFUD_DUMMY_DATA;
}
if(HAL_SPI_TransmitReceive(spi_dev->spix,&send_data,&read_data,1,1000) != 0x00)
goto exit;
if (i >= write_size) {
*read_buf++ = read_data;
}
}
exit:
HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_SET);
return result;
}
/* about 100 microsecond delay */
static void retry_delay_100us(void) {
uint32_t delay = 120;
while(delay--);
}
static spi_user_data spi1 = { .spix = &hspi1, .cs_gpiox = FLASH_SPI_CS_GPIO_Port, .cs_gpio_pin = FLASH_SPI_CS_Pin };
sfud_err sfud_spi_port_init(sfud_flash *flash) {
sfud_err result = SFUD_SUCCESS;
switch (flash->index) {
case SFUD_MX25L128_DEVICE_INDEX: {
rcc_configuration(&spi1);
gpio_configuration(&spi1);
spi_configuration(&spi1);
flash->spi.wr = spi_write_read;
flash->spi.lock = spi_lock;
flash->spi.unlock = spi_unlock;
flash->spi.user_data = &spi1;
flash->retry.delay = retry_delay_100us;
/* adout 60 seconds timeout */
flash->retry.times = 60 * 10000;
break;
}
}
return result;
}
2.1.2 获取FLASH设备参数
如果用户没有定义FLASH设备参数(各种ID、写入方式等),那么SFUD将会读取FLASH设备的JEDEC ID(即制造商ID、类型ID、容量ID),如果读取成功,那么会用SFDP方式去读取FLASH设备参数表(前提是SFUD_USING_SFDP宏定义打开),如果读取成功,那么不管SFUD_USING_FLASH_INFO_TABLE宏定义是否打开,都不会将用户定义的FLASH设备参数赋值给SFUD,除非SFDP读取设备参数表失败,才会将将用户定义的FLASH设备参数赋值给SFUD。然后将获取到的FLASH名称、大小通过串口打印出来,如果获取失败则会提示这款FLASH不支持或未找到FLASH。如果以上步骤都成功了,最后SFUD会复位FLASH设备,如果FLASH支持AAI读取模式,那么SFUD会写入命令状态0x00去除FLASH的块保护,如果FLASH设备大于16MB,那么SFUD将进入4字节地址模式。
注意,上述过程只要有一步失败,那么hardware_init()就会立刻返回失败的类型,移植完调试的时候跟进去调试hardware_init()就好了。
2.2 software_init
software_init只是对上一步hardware_init()的一个检验,即检查是否找到了flash设备,可见下面的程序。SFUD_ASSERT是sfud_def.h中定义一个宏,如果打开了DEBUG模式,那么当SFUD_ASSERT判断的条件成立时会打印调试信息并进入while(1)死循环,否则就是空的宏。
/**
* software initialize
*
* @param flash flash device
*
* @return result
*/
static sfud_err software_init(const sfud_flash *flash) {
sfud_err result = SFUD_SUCCESS;
SFUD_ASSERT(flash);
return result;
}
3. SFUD写FLASH
以下内容全部是针对FLASH小于16MB而言的,即flash->addr_in_4_byte=true的情况。
SFUD写函数具体为sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data),其入口参数分别为flash设备指针、要写入的起始地址、写入的字节数、要写入的数据头指针。SFUD支持两种写入模式,即单字节或256字节写入模式和AAI写入模式,因此sfud_write函数根据不同的写入模式执行不同的写入操作。
SFUD还提供一个函数叫做sfud_erase_write,其功能就是在写入前先调用sfud_erase擦除扇区,再调用sfud_write写入。注意:FLASH只能将1写为0,且FLASH的最小擦除单位为扇区,一般是4KB,在写入前要考虑好对已有数据的保护。
/**
* write flash data (no erase operate)
*
* @param flash flash device
* @param addr start address
* @param size write size
* @param data write data
*
* @return result
*/
sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data) {
sfud_err result = SFUD_SUCCESS;
if (flash->chip.write_mode & SFUD_WM_PAGE_256B) {
result = page256_or_1_byte_write(flash, addr, size, 256, data);
} else if (flash->chip.write_mode & SFUD_WM_AAI) {
result = aai_write(flash, addr, size, data);
} else if (flash->chip.write_mode & SFUD_WM_DUAL_BUFFER) {
}
return result;
}
3.1 单字节或256字节写入模式
函数原型是static sfud_err page256_or_1_byte_write(const sfud_flash *flash, uint32_t addr, size_t size, uint16_t write_gran, const uint8_t *data),入口参数比sfud_write多了一个write_gran,即单次写入的字节数。sfud_write调用该函数时已经将其固定为256。
函数首先进行一系列有效性判断(比如写入的字节数是否超区域),确认无误后,发送写使能命令0x06,这个写入命令是通过函数指针调用的,即result = flash->spi.wr(&flash->spi, &cmd, 1, NULL, 0),spi.wr即图1-1里初始化时将spi_write_read这个函数的指针赋值过去的,spi_write_read的原型是static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,size_t read_size),它是一个spi的读写函数。
然后,将cmd_data[0]赋值为0x02(SFUD_CMD_PAGE_PROGRAM命令),cmd_data是一个unsigned char类型的数组,其作用是:cmd_data[0]存放SFUD_CMD_PAGE_PROGRAM命令,通过make_adress_byte_array函数将要写入的起始地址的16-23位、8-15位、0-7位分别放在cmd_data[1]、cmd_data[2]、cmd_data[3]中,随后的257位拷贝要写入的数组(不一定一次拷贝256位,由是否256字节跨页写入和是否单字节写入来决定),最后会将整个cmd_data数组(包含了命令和数据)一起通过spi->wr函数写入到FLASH中。中间还插入了一段判断是否跨页写入的算法。最后,写入完成后失能写入。
/**
* write flash data (no erase operate) for write 1 to 256 bytes per page mode or byte write mode
*
* @param flash flash device
* @param addr start address
* @param size write size
* @param write_gran write granularity bytes, only support 1 or 256
* @param data write data
*
* @return result
*/
static sfud_err page256_or_1_byte_write(const sfud_flash *flash, uint32_t addr, size_t size, uint16_t write_gran,
const uint8_t *data) {
sfud_err result = SFUD_SUCCESS;
const sfud_spi *spi = &flash->spi;
uint8_t cmd_data[5 + SFUD_WRITE_MAX_PAGE_SIZE], cmd_size;
size_t data_size;
SFUD_ASSERT(flash);
/* only support 1 or 256 */
SFUD_ASSERT(write_gran == 1 || write_gran == 256);
/* must be call this function after initialize OK */
SFUD_ASSERT(flash->init_ok);
/* check the flash address bound */
if (addr + size > flash->chip.capacity) {
SFUD_INFO("Error: Flash address is out of bound.");
return SFUD_ERR_ADDR_OUT_OF_BOUND;
}
/* lock SPI */
if (spi->lock) {
spi->lock(spi);
}
/* loop write operate. write unit is write granularity */
while (size) {
/* set the flash write enable */
result = set_write_enabled(flash, true);
if (result != SFUD_SUCCESS) {
goto __exit;
}
cmd_data[0] = SFUD_CMD_PAGE_PROGRAM;
make_adress_byte_array(flash, addr, &cmd_data[1]);
cmd_size = flash->addr_in_4_byte ? 5 : 4;
/* make write align and calculate next write address */
if (addr % write_gran != 0) {
if (size > write_gran - (addr % write_gran)) {
data_size = write_gran - (addr % write_gran);
} else {
data_size = size;
}
} else {
if (size > write_gran) {
data_size = write_gran;
} else {
data_size = size;
}
}
size -= data_size;
addr += data_size;
memcpy(&cmd_data[cmd_size], data, data_size);
result = spi->wr(spi, cmd_data, cmd_size + data_size, NULL, 0);
if (result != SFUD_SUCCESS) {
SFUD_INFO("Error: Flash write SPI communicate error.");
goto __exit;
}
result = wait_busy(flash);
if (result != SFUD_SUCCESS) {
goto __exit;
}
data += data_size;
}
__exit:
/* set the flash write disable */
set_write_enabled(flash, false);
/* unlock SPI */
if (spi->unlock) {
spi->unlock(spi);
}
return result;
}
3.2 AAI写入模式
函数原型是static sfud_err aai_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data),入口参数与sfud_write完全相同。AAI写入模式要求写入的起始地址为偶数,当起始地址为奇数时,aai_write先调用page256_or_1_byte_write写入第一个字节,使之后需要写入的起始地址变为偶数。
满足地址为偶数条件后,将cmd_data[0]赋值为0xAD(SFUD_CMD_AAI_WORD_PROGRAM命令),之后的每次会向FLASH写入2个字节。当第一次写入两个字节时,类似于page256_or_1_byte_write函数,通过make_adress_byte_array函数将要写入的起始地址的16-23位、8-15位、0-7位分别放在cmd_data[1]、cmd_data[2]、cmd_data[3]中,cmd_data[4]和cmd_data[5]存放前两个数据,然后通过spi->wr函数写入到FLASH中。后续的写入则不需要指定起始地址了,因此cmd_data只用到3个字节,即cmd_data[0]不变,cmd_data[1] h和cmd_data[2]存放两个字节数据。每次发送两个字节,直至发送完毕。如果最后的一个字节是奇数,那么会通过单字节写入模式去写入到FLASH中。
/**
* write flash data (no erase operate) for auto address increment mode
*
* If the address is odd number, it will place one 0xFF before the start of data for protect the old data.
* If the latest remain size is 1, it will append one 0xFF at the end of data for protect the old data.
*
* @param flash flash device
* @param addr start address
* @param size write size
* @param data write data
*
* @return result
*/
static sfud_err aai_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data) {
sfud_err result = SFUD_SUCCESS;
const sfud_spi *spi = &flash->spi;
uint8_t cmd_data[6], cmd_size;
bool first_write = true;
SFUD_ASSERT(flash);
SFUD_ASSERT(flash->init_ok);
/* check the flash address bound */
if (addr + size > flash->chip.capacity) {
SFUD_INFO("Error: Flash address is out of bound.");
return SFUD_ERR_ADDR_OUT_OF_BOUND;
}
/* lock SPI */
if (spi->lock) {
spi->lock(spi);
}
/* The address must be even for AAI write mode. So it must write one byte first when address is odd. */
if (addr % 2 != 0) {
result = page256_or_1_byte_write(flash, addr++, 1, 1, data++);
if (result != SFUD_SUCCESS) {
goto __exit;
}
size--;
}
/* set the flash write enable */
result = set_write_enabled(flash, true);
if (result != SFUD_SUCCESS) {
goto __exit;
}
/* loop write operate. */
cmd_data[0] = SFUD_CMD_AAI_WORD_PROGRAM;
while (size >= 2) {
if (first_write) {
make_adress_byte_array(flash, addr, &cmd_data[1]);
cmd_size = flash->addr_in_4_byte ? 5 : 4;
cmd_data[cmd_size] = *data;
cmd_data[cmd_size + 1] = *(data + 1);
first_write = false;
} else {
cmd_size = 1;
cmd_data[1] = *data;
cmd_data[2] = *(data + 1);
}
result = spi->wr(spi, cmd_data, cmd_size + 2, NULL, 0);
if (result != SFUD_SUCCESS) {
SFUD_INFO("Error: Flash write SPI communicate error.");
goto __exit;
}
result = wait_busy(flash);
if (result != SFUD_SUCCESS) {
goto __exit;
}
size -= 2;
addr += 2;
data += 2;
}
/* set the flash write disable for exit AAI mode */
result = set_write_enabled(flash, false);
/* write last one byte data when origin write size is odd */
if (result == SFUD_SUCCESS && size == 1) {
result = page256_or_1_byte_write(flash, addr, 1, 1, data);
}
__exit:
if (result != SFUD_SUCCESS) {
set_write_enabled(flash, false);
}
/* unlock SPI */
if (spi->unlock) {
spi->unlock(spi);
}
return result;
}
4. SFUD读FLASH
函数原型为sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data),将读取的数据放在data指向的数组里。函数先进行一系列有效性判断(例如读取的地址是否超出了FLASH容量),然后将0X03(SFUD_CMD_READ_DATA)赋值给cmd_data[0],同样的,将地址对齐后分别赋值给cmd_data[1]、cmd_data[2]、cmd_data[3],最后将cmd_data通过spi->wr函数指针写入FLASH,读取出数据。
/**
* read flash data
*
* @param flash flash device
* @param addr start address
* @param size read size
* @param data read data pointer
*
* @return result
*/
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data) {
sfud_err result = SFUD_SUCCESS;
const sfud_spi *spi = &flash->spi;
uint8_t cmd_data[5], cmd_size;
SFUD_ASSERT(flash);
SFUD_ASSERT(data);
/* must be call this function after initialize OK */
SFUD_ASSERT(flash->init_ok);
/* check the flash address bound */
if (addr + size > flash->chip.capacity) {
SFUD_INFO("Error: Flash address is out of bound.");
return SFUD_ERR_ADDR_OUT_OF_BOUND;
}
/* lock SPI */
if (spi->lock) {
spi->lock(spi);
}
result = wait_busy(flash);
if (result == SFUD_SUCCESS) {
cmd_data[0] = SFUD_CMD_READ_DATA;
make_adress_byte_array(flash, addr, &cmd_data[1]);
cmd_size = flash->addr_in_4_byte ? 5 : 4;
result = spi->wr(spi, cmd_data, cmd_size, data, size);
}
/* unlock SPI */
if (spi->unlock) {
spi->unlock(spi);
}
return result;
}
5. SFUD擦除FLASH
SFUD提供的擦除函数有两个,一个是全片擦除函数,另一个是区域擦除函数。
5.1 全片擦除
函数原型为sfud_err sfud_chip_erase(const sfud_flash *flash)。函数比较简单,通过发送0xC7(SFUD_CMD_ERASE_CHIP)命令即可擦除全片,程序还针对具有双缓冲的FLASH芯片做了特殊处理:依次发送0xC7、0x94、0x80、0x9A来完成全片擦除。
/**
* erase all flash data
*
* @param flash flash device
*
* @return result
*/
sfud_err sfud_chip_erase(const sfud_flash *flash) {
sfud_err result = SFUD_SUCCESS;
const sfud_spi *spi = &flash->spi;
uint8_t cmd_data[4];
SFUD_ASSERT(flash);
/* must be call this function after initialize OK */
SFUD_ASSERT(flash->init_ok);
/* lock SPI */
if (spi->lock) {
spi->lock(spi);
}
/* set the flash write enable */
result = set_write_enabled(flash, true);
if (result != SFUD_SUCCESS) {
goto __exit;
}
cmd_data[0] = SFUD_CMD_ERASE_CHIP;
/* dual-buffer write, like AT45DB series flash chip erase operate is different for other flash */
if (flash->chip.write_mode & SFUD_WM_DUAL_BUFFER) {
cmd_data[1] = 0x94;
cmd_data[2] = 0x80;
cmd_data[3] = 0x9A;
result = spi->wr(spi, cmd_data, 4, NULL, 0);
} else {
result = spi->wr(spi, cmd_data, 1, NULL, 0);
}
if (result != SFUD_SUCCESS) {
SFUD_INFO("Error: Flash chip erase SPI communicate error.");
goto __exit;
}
result = wait_busy(flash);
__exit:
/* set the flash write disable */
set_write_enabled(flash, false);
/* unlock SPI */
if (spi->unlock) {
spi->unlock(spi);
}
return result;
}
5.2 区域擦除
函数原型为sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size)。函数先进行一系列有效性判断(例如擦除的地址是否超出了FLASH容量),然后判断要擦除的起始地址是否是0,要擦除的大小是否是FLASH的容量大小,如果两个同时满足,则通过return方式调用sfud_chip_erase函数进行全片擦除。
如果起始地址和擦除大小不满足全片擦除的条件,那么程序会继续往下执行区域擦除。如果打开了SFDP宏定义,那么会优先通过SFDP方式获取擦除命令和擦除字节大小,然后将cur_erase_cmd命令赋值给cmd_data[0],要擦除的地址对齐后分别赋值给cmd_data[1]、cmd_data[2]、cmd_data[3],再通过通过spi->wr函数指针写入FLASH完成一次区域擦除,再计算下一次需要擦除的地址并重复上述操作,直至指定的区域擦除完毕。
/**
* erase flash data
*
* @note It will erase align by erase granularity.
*
* @param flash flash device
* @param addr start address
* @param size erase size
*
* @return result
*/
sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size) {
extern size_t sfud_sfdp_get_suitable_eraser(const sfud_flash *flash, uint32_t addr, size_t erase_size);
sfud_err result = SFUD_SUCCESS;
const sfud_spi *spi = &flash->spi;
uint8_t cmd_data[5], cmd_size, cur_erase_cmd;
size_t cur_erase_size;
SFUD_ASSERT(flash);
/* must be call this function after initialize OK */
SFUD_ASSERT(flash->init_ok);
/* check the flash address bound */
if (addr + size > flash->chip.capacity) {
SFUD_INFO("Error: Flash address is out of bound.");
return SFUD_ERR_ADDR_OUT_OF_BOUND;
}
if (addr == 0 && size == flash->chip.capacity) {
return sfud_chip_erase(flash);
}
/* lock SPI */
if (spi->lock) {
spi->lock(spi);
}
/* loop erase operate. erase unit is erase granularity */
while (size) {
/* if this flash is support SFDP parameter, then used SFDP parameter supplies eraser */
#ifdef SFUD_USING_SFDP
size_t eraser_index;
if (flash->sfdp.available) {
/* get the suitable eraser for erase process from SFDP parameter */
eraser_index = sfud_sfdp_get_suitable_eraser(flash, addr, size);
cur_erase_cmd = flash->sfdp.eraser[eraser_index].cmd;
cur_erase_size = flash->sfdp.eraser[eraser_index].size;
} else {
#else
{
#endif
cur_erase_cmd = flash->chip.erase_gran_cmd;
cur_erase_size = flash->chip.erase_gran;
}
/* set the flash write enable */
result = set_write_enabled(flash, true);
if (result != SFUD_SUCCESS) {
goto __exit;
}
cmd_data[0] = cur_erase_cmd;
make_adress_byte_array(flash, addr, &cmd_data[1]);
cmd_size = flash->addr_in_4_byte ? 5 : 4;
result = spi->wr(spi, cmd_data, cmd_size, NULL, 0);
if (result != SFUD_SUCCESS) {
SFUD_INFO("Error: Flash erase SPI communicate error.");
goto __exit;
}
result = wait_busy(flash);
if (result != SFUD_SUCCESS) {
goto __exit;
}
/* make erase align and calculate next erase address */
if (addr % cur_erase_size != 0) {
if (size > cur_erase_size - (addr % cur_erase_size)) {
size -= cur_erase_size - (addr % cur_erase_size);
addr += cur_erase_size - (addr % cur_erase_size);
} else {
goto __exit;
}
} else {
if (size > cur_erase_size) {
size -= cur_erase_size;
addr += cur_erase_size;
} else {
goto __exit;
}
}
}
__exit:
/* set the flash write disable */
set_write_enabled(flash, false);
/* unlock SPI */
if (spi->unlock) {
spi->unlock(spi);
}
return result;
}