SFUD源码浅析

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初始化配置

2. SFUD初始化

2.1 hardware_init

2.1.1 SPI初始化

2.1.2 获取FLASH设备参数

2.2 software_init

3. SFUD写FLASH

3.1 单字节或256字节写入模式

3.2 AAI写入模式

4. SFUD读FLASH

5. SFUD擦除FLASH

5.1 全片擦除

5.2 区域擦除


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可以不打开从而减小程序大小。

图1 W25Q128关于0x5A命令的描述

 

图2 0x5A命令时序图

 

用户需要事先在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;
}

 

  • 10
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dokin丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值