IOT-OS之RT-Thread(十一)--- FAL分區管理與easyflash變量管理

一、FAL管理與示例

FAL (Flash Abstraction Layer) Flash 抽象層,是對 Flash 及基於 Flash 的分區進行管理、操作的抽象層,對上層統一了 Flash 及 分區操作的 API ,FAL 框架圖如下:
FAL框架圖
從上圖可以看出FAL抽象層位於SFUD框架的上層,可以將多個Flash硬件(包括片內Flash和片外Flash)統一進行管理,並向上層比如DFS文件系統層提供對底層多個Flash硬件的統一訪問接口,方便上層應用對底層硬件的訪問操作。

1.1 FAL軟件包源碼獲取

從Github下載的RT-Thread源碼中並沒有FAL軟件包的源碼,我們可以通過git工具獲取,menuconfig中也配置了FAL軟件包的配置項,在配置項內有FAL軟件包的下載地址等信息,我們配置啓用FAL軟件包後即可通過git工具(前提是需要安裝並配置後Git工具)自動下載FAL軟件包源碼到工程目錄。

在前篇博客的工程目錄stm32l475_dfs_sample中打開env環境,運行menuconfig命令,啓用FAL配置界面如下:
啓用FAL軟件包的配置界面
啓用FAL軟件包後出現更多選項,我們想使用FAL管理STM32L475片內Flash和W25Q128 Flash,其中W25Q128使用SFUD框架驅動,在啓用FAL軟件包後,我們可以選擇使用SFUD驅動,SFUD設備名默認爲norflash0,可以更改設備名爲W25Q128,使用SFUD驅動的配置界面如下:
FAL使用SFUD的配置界面
保存配置退出後,在env環境執行pkgs --update命令,會自動從FAL的github倉庫獲取FAL軟件包源碼到本地工程目錄,如下圖所示:
獲取FAL軟件包
獲取到的FAL軟件包目錄結構如下:
FAL目錄結構

1.2 FAL管理

FAL既然是對多個Flash設備進行分區管理的,自然會對Flash設備和分區有相應的數據結構描述。

  • FAL設備與分區控制塊

FAL設備與FAL分區的描述結構體如下:

// projects\stm32l475_dfs_sample\packages\fal-latest\inc\fal_def.h

/* FAL flash and partition device name max length */
#ifndef FAL_DEV_NAME_MAX
#define FAL_DEV_NAME_MAX 24
#endif

struct fal_flash_dev
{
    char name[FAL_DEV_NAME_MAX];

    /* flash device start address and len  */
    uint32_t addr;
    size_t len;
    /* the block size in the flash for erase minimum granularity */
    size_t blk_size;

    struct
    {
        int (*init)(void);
        int (*read)(long offset, uint8_t *buf, size_t size);
        int (*write)(long offset, const uint8_t *buf, size_t size);
        int (*erase)(long offset, size_t size);
    } ops;
};
typedef struct fal_flash_dev *fal_flash_dev_t;

/**
 * FAL partition
 */
struct fal_partition
{
    uint32_t magic_word;

    /* partition name */
    char name[FAL_DEV_NAME_MAX];
    /* flash device name for partition */
    char flash_name[FAL_DEV_NAME_MAX];

    /* partition offset address on flash device */
    long offset;
    size_t len;

    uint32_t reserved;
};
typedef struct fal_partition *fal_partition_t;

fal_flash_dev結構體除了包含設備名、起始地址、長度、塊大小等對flash設備的描述參數,還包括對flash設備的操作函數指針,這些操作函數需要在移植FAL時由下層的驅動實現。

fal_partition結構體則包含分區名、設備名、分區在設備上的偏移地址和長度等,從該結構體定義也可以看出,一個fal_partition分區不能跨flash設備分配。

  • FAL初始化過程

瞭解FAL原理,先從FAL組件初始化過程開始:

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal.c

/**
 * FAL (Flash Abstraction Layer) initialization.
 * It will initialize all flash device and all flash partition.
 *
 * @return >= 0: partitions total number
 */
int fal_init(void)
{
    extern int fal_flash_init(void);
    extern int fal_partition_init(void);

    int result;

    /* initialize all flash device on FAL flash table */
    result = fal_flash_init();

    if (result < 0) {
        goto __exit;
    }

    /* initialize all flash partition on FAL partition table */
    result = fal_partition_init();

__exit:

    if ((result > 0) && (!init_ok))
    {
        init_ok = 1;
        log_i("RT-Thread Flash Abstraction Layer (V%s) initialize success.", FAL_SW_VERSION);
    }
    else if(result <= 0)
    {
        init_ok = 0;
        log_e("RT-Thread Flash Abstraction Layer (V%s) initialize failed.", FAL_SW_VERSION);
    }

    return result;
}


// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_flash.c

static const struct fal_flash_dev * const device_table[] = FAL_FLASH_DEV_TABLE;
static const size_t device_table_len = sizeof(device_table) / sizeof(device_table[0]);
static uint8_t init_ok = 0;

/**
 * Initialize all flash device on FAL flash table
 *
 * @return result
 */
int fal_flash_init(void)
{
    size_t i;

    if (init_ok)
    {
        return 0;
    }

    for (i = 0; i < device_table_len; i++)
    {
        assert(device_table[i]->ops.read);
        assert(device_table[i]->ops.write);
        assert(device_table[i]->ops.erase);
        /* init flash device on flash table */
        if (device_table[i]->ops.init)
        {
            device_table[i]->ops.init();
        }
        ......
    }
    init_ok = 1;
    return 0;
}


// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c

USED static const struct fal_partition partition_table_def[] SECTION("FalPartTable") = FAL_PART_TABLE;
static const struct fal_partition *partition_table = NULL;

/**
 * Initialize all flash partition on FAL partition table
 *
 * @return partitions total number
 */
int fal_partition_init(void)
{
    size_t i;
    const struct fal_flash_dev *flash_dev = NULL;

    if (init_ok)
    {
        return partition_table_len;
    }

#ifdef FAL_PART_HAS_TABLE_CFG
    partition_table = &partition_table_def[0];
    partition_table_len = sizeof(partition_table_def) / sizeof(partition_table_def[0]);
#else
    /* load partition table from the end address FAL_PART_TABLE_END_OFFSET, error return 0 */
    long part_table_offset = FAL_PART_TABLE_END_OFFSET;
    size_t table_num = 0, table_item_size = 0;
    uint8_t part_table_find_ok = 0;
    uint32_t read_magic_word;
    fal_partition_t new_part = NULL;

    flash_dev = fal_flash_device_find(FAL_PART_TABLE_FLASH_DEV_NAME);
    if (flash_dev == NULL)
    {
        log_e("Initialize failed! Flash device (%s) NOT found.", FAL_PART_TABLE_FLASH_DEV_NAME);
        goto _exit;
    }

    /* check partition table offset address */
    if (part_table_offset < 0 || part_table_offset >= (long) flash_dev->len)
    {
        log_e("Setting partition table end offset address(%ld) out of flash bound(<%d).", part_table_offset, flash_dev->len);
        goto _exit;
    }

    table_item_size = sizeof(struct fal_partition);
    new_part = (fal_partition_t)FAL_MALLOC(table_item_size);
    if (new_part == NULL)
    {
        log_e("Initialize failed! No memory for table buffer.");
        goto _exit;
    }

    /* find partition table location */
    {
        uint8_t read_buf[64];

        part_table_offset -= sizeof(read_buf);
        while (part_table_offset >= 0)
        {
            if (flash_dev->ops.read(part_table_offset, read_buf, sizeof(read_buf)) > 0)
            {
                /* find magic word in read buf */
                for (i = 0; i < sizeof(read_buf) - sizeof(read_magic_word) + 1; i++)
                {
                    read_magic_word = read_buf[0 + i] + (read_buf[1 + i] << 8) + (read_buf[2 + i] << 16) + (read_buf[3 + i] << 24);
                    if (read_magic_word == ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L))
                    {
                        part_table_find_ok = 1;
                        part_table_offset += i;
                        log_d("Find the partition table on '%s' offset @0x%08lx.", FAL_PART_TABLE_FLASH_DEV_NAME,
                                part_table_offset);
                        break;
                    }
                }
            }
            else
            {
                /* read failed */
                break;
            }

            if (part_table_find_ok)
            {
                break;
            }
            else
            {
                /* calculate next read buf position */
                if (part_table_offset >= (long)sizeof(read_buf))
                {
                    part_table_offset -= sizeof(read_buf);
                    part_table_offset += (sizeof(read_magic_word) - 1);
                }
                else if (part_table_offset != 0)
                {
                    part_table_offset = 0;
                }
                else
                {
                    /* find failed */
                    break;
                }
            }
        }
    }

    /* load partition table */
    while (part_table_find_ok)
    {
        memset(new_part, 0x00, table_num);
        if (flash_dev->ops.read(part_table_offset - table_item_size * (table_num), (uint8_t *) new_part,
                table_item_size) < 0)
        {
            log_e("Initialize failed! Flash device (%s) read error!", flash_dev->name);
            table_num = 0;
            break;
        }

        if (new_part->magic_word != ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L))
        {
            break;
        }

        partition_table = (fal_partition_t) FAL_REALLOC(partition_table, table_item_size * (table_num + 1));
        if (partition_table == NULL)
        {
            log_e("Initialize failed! No memory for partition table");
            table_num = 0;
            break;
        }

        memcpy(partition_table + table_num, new_part, table_item_size);

        table_num++;
    };

    if (table_num == 0)
    {
        log_e("Partition table NOT found on flash: %s (len: %d) from offset: 0x%08x.", FAL_PART_TABLE_FLASH_DEV_NAME,
                FAL_DEV_NAME_MAX, FAL_PART_TABLE_END_OFFSET);
        goto _exit;
    }
    else
    {
        partition_table_len = table_num;
    }
#endif /* FAL_PART_HAS_TABLE_CFG */

    /* check the partition table device exists */

    for (i = 0; i < partition_table_len; i++)
    {
        flash_dev = fal_flash_device_find(partition_table[i].flash_name);
        if (flash_dev == NULL)
        {
            log_d("Warning: Do NOT found the flash device(%s).", partition_table[i].flash_name);
            continue;
        }

        if (partition_table[i].offset >= (long)flash_dev->len)
        {
            log_e("Initialize failed! Partition(%s) offset address(%ld) out of flash bound(<%d).",
                    partition_table[i].name, partition_table[i].offset, flash_dev->len);
            partition_table_len = 0;
            goto _exit;
        }
    }

    init_ok = 1;

_exit:

#if FAL_DEBUG
    fal_show_part_table();
#endif

#ifndef FAL_PART_HAS_TABLE_CFG
    if (new_part)
    {
        FAL_FREE(new_part);
    }
#endif /* !FAL_PART_HAS_TABLE_CFG */

    return partition_table_len;
}

FAL組件初始化最重要的是維護兩個表:一個是flash設備表;另一個是FAL分區表,兩個表的元素分別是前面介紹過的fal_flash_dev結構體地址和fal_partition結構體對象。

fal_flash_dev設備表主要由底層的Flash驅動(包括MCU片內Flash和SFUD驅動的片外Flash)提供,也即FAL移植的重點就是在Flash驅動層向FAL提供fal_flash_dev設備表,每個flash設備提供設備表中的一個元素。

fal_partition分區表由用戶事先配置在fal_cfg.h頭文件中,FAL向上面的用戶層提供的分區訪問接口函數操作的內存區間就是從fal_partition分區表獲取的,最後對分區的訪問還是通過Flash驅動提供的接口函數(fal_flash_dev.ops)實現的。

  • FAL分區管理接口

FAL主要是進行分區管理的,所以嚮應用層提供的接口函數主要也是對分區的訪問,Flash分區訪問接口函數要想訪問到Flash硬件設備,最終需要調用Flash驅動向FAL提供的接口函數指針實現,FAL分區訪問接口函數聲明如下:

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_flash.c

/**
 * find flash device by name
 *
 * @param name flash device name
 *
 * @return != NULL: flash device
 *            NULL: not found
 */
const struct fal_flash_dev *fal_flash_device_find(const char *name);


// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c

/**
 * find the partition by name
 *
 * @param name partition name
 *
 * @return != NULL: partition
 *            NULL: not found
 */
const struct fal_partition *fal_partition_find(const char *name);

/**
 * get the partition table
 *
 * @param len return the partition table length
 *
 * @return partition table
 */
const struct fal_partition *fal_get_partition_table(size_t *len);

/**
 * set partition table temporarily
 * This setting will modify the partition table temporarily, the setting will be lost after restart.
 *
 * @param table partition table
 * @param len partition table length
 */
void fal_set_partition_table_temp(struct fal_partition *table, size_t len);

/**
 * read data from partition
 *
 * @param part partition
 * @param addr relative address for partition
 * @param buf read buffer
 * @param size read size
 *
 * @return >= 0: successful read data size
 *           -1: error
 */
int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size);

/**
 * write data to partition
 *
 * @param part partition
 * @param addr relative address for partition
 * @param buf write buffer
 * @param size write size
 *
 * @return >= 0: successful write data size
 *           -1: error
 */
int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size);

/**
 * erase partition data
 *
 * @param part partition
 * @param addr relative address for partition
 * @param size erase size
 *
 * @return >= 0: successful erased data size
 *           -1: error
 */
int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size);

/**
 * erase partition all data
 *  * @param part partition
 *  * @return >= 0: successful erased data size
 *           -1: error
 */
int fal_partition_erase_all(const struct fal_partition *part);
  • FAL分區轉設備接口

前篇博客介紹DFS elmfat文件系統時談到,elmfat文件系統只能掛載到塊設備上,FAL管理的分區只是一段連續的flash存儲空間,並不是一個塊設備。前篇博客將DFS elmfat文件系統掛載到W25Q128 Flash上,實際就是掛載到一個塊設備上,SFUD將W25Q128 Flash註冊爲一個塊設備,所以可以順利掛載。

有時候我們想在一個Flash設備上分出多個空間分別用於不同的用途,比如FAL框架圖中展示的,一部分空間用於掛載文件系統,一部分空間用於存儲非易失配置參數,另一部分空間用於存儲OTA文件。這就需要把一個flash物理設備轉換爲多個邏輯設備,FAL便提供了將flash分區轉換爲BLK/MTD/Char設備的功能。

FAL分區轉換BLK/MTD/Char設備的過程有很大的類似性,這裏以FAL分區轉BLK塊設備爲例,說明其工作原理。首先看FAL塊設備的數據結構描述:

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c

struct fal_blk_device
{
    struct rt_device                parent;
    struct rt_device_blk_geometry   geometry;
    const struct fal_partition     *fal_part;
};

接着看FAL塊設備的創建與註冊過程:

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c

/**
 * create RT-Thread block device by specified partition
 *
 * @param parition_name partition name
 *
 * @return != NULL: created block device
 *            NULL: created failed
 */
struct rt_device *fal_blk_device_create(const char *parition_name)
{
    struct fal_blk_device *blk_dev;
    const struct fal_partition *fal_part = fal_partition_find(parition_name);
    const struct fal_flash_dev *fal_flash = NULL;

    if (!fal_part)
    {
        log_e("Error: the partition name (%s) is not found.", parition_name);
        return NULL;
    }

    if ((fal_flash = fal_flash_device_find(fal_part->flash_name)) == NULL)
    {
        log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name);
        return NULL;
    }

    blk_dev = (struct fal_blk_device*) rt_malloc(sizeof(struct fal_blk_device));
    if (blk_dev)
    {
        blk_dev->fal_part = fal_part;
        blk_dev->geometry.bytes_per_sector = fal_flash->blk_size;
        blk_dev->geometry.block_size = fal_flash->blk_size;
        blk_dev->geometry.sector_count = fal_part->len / fal_flash->blk_size;

        /* register device */
        blk_dev->parent.type = RT_Device_Class_Block;

#ifdef RT_USING_DEVICE_OPS
        blk_dev->parent.ops  = &blk_dev_ops;
#else
        blk_dev->parent.init = NULL;
        blk_dev->parent.open = NULL;
        blk_dev->parent.close = NULL;
        blk_dev->parent.read = blk_dev_read;
        blk_dev->parent.write = blk_dev_write;
        blk_dev->parent.control = blk_dev_control;
#endif

        /* no private */
        blk_dev->parent.user_data = RT_NULL;

        log_i("The FAL block device (%s) created successfully", fal_part->name);
        rt_device_register(RT_DEVICE(blk_dev), fal_part->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);
    }
    else
    {
        log_e("Error: no memory for create FAL block device");
    }

    return RT_DEVICE(blk_dev);
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops blk_dev_ops =
{
    RT_NULL,
    RT_NULL,
    RT_NULL,
    blk_dev_read,
    blk_dev_write,
    blk_dev_control
};
#endif

FAL塊設備的創建跟 I / O設備模型框架中設備的創建註冊過程類似,主要是還是向 I / O設備模型框架註冊一個塊設備及其接口函數,使該設備可以通過 I / O設備管理接口訪問。

FAL創建塊設備向I / O設備管理層註冊的訪問接口函數最終調用的是FAL分區訪問接口函數,下面以塊設備寫入接口函數實現過程爲例進行說明:

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c

static rt_size_t blk_dev_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)
{
    int ret = 0;
    struct fal_blk_device *part;
    rt_off_t phy_pos;
    rt_size_t phy_size;

    part = (struct fal_blk_device*) dev;
    assert(part != RT_NULL);

    /* change the block device's logic address to physical address */
    phy_pos = pos * part->geometry.bytes_per_sector;
    phy_size = size * part->geometry.bytes_per_sector;

    ret = fal_partition_erase(part->fal_part, phy_pos, phy_size);

    if (ret == (int) phy_size)
    {
        ret = fal_partition_write(part->fal_part, phy_pos, buffer, phy_size);
    }

    if (ret != (int) phy_size)
    {
        ret = 0;
    }
    else
    {
        ret = size;
    }

    return ret;
}


// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c

int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size)
{
    int ret = 0;
    const struct fal_flash_dev *flash_dev = NULL;

    assert(part);
    assert(buf);

    if (addr + size > part->len)
    {
        log_e("Partition write error! Partition address out of bound.");
        return -1;
    }

    flash_dev = fal_flash_device_find(part->flash_name);
    if (flash_dev == NULL)
    {
        log_e("Partition write error!  Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name);
        return -1;
    }

    ret = flash_dev->ops.write(part->offset + addr, buf, size);
    if (ret < 0)
    {
        log_e("Partition write error! Flash device(%s) write error!", part->flash_name);
    }

    return ret;
}

1.3 FAL移植

前面介紹了FAL移植的關鍵是向其提供fal_flash_dev設備表,也相當於flash驅動層向FAL抽象層提供該flash設備的參數及訪問接口函數。

考慮到packages下面的軟件版本後續可能會升級覆蓋,我們不在\packages\fal-latest目錄下直接進行移植修改,而是在packages目錄外新建一個文件夾ports專門保存軟件包的移植文件信息。

新建與packages軟件包同級的移植文件目錄ports,將packages\fal-latest\samples\porting目錄下的fal_cfg.h與fal_flash_sfud_port.c文件複製一份到ports\fal目錄下,將packages\fal-latest\SConscript複製一份到ports\fal目錄下,將packages\SConscript複製一份到ports目錄下,複製文件後的目錄結構如下圖所示:
新建移植文件目錄ports
由於不再使用packages\fal-latest\samples\porting目錄下的移植文件,可以將packages\fal-latest\SConscript文件中如下的代碼刪除:

// projects\stm32l475_dfs_sample\packages\fal-latest\SConscript
/* Delete the code below */
if GetDepend(['FAL_USING_SFUD_PORT']):
    src += Glob('samples\porting\fal_flash_sfud_port.c')

由於ports\fal目錄及下面的文件名有變化,所以需要修改編譯腳本ports\fal\SConscript,主要是修改文件目錄及文件名,修改後的編譯腳本如下:

// projects\stm32l475_dfs_sample\ports\fal\SConscript

from building import *
import rtconfig

cwd     = GetCurrentDir()
src     = []
CPPPATH = [cwd]
LOCAL_CCFLAGS = ''

if GetDepend(['FAL_USING_SFUD_PORT']):
    src += Glob('fal_flash_sfud_port.c')

if rtconfig.CROSS_TOOL == 'gcc':
    LOCAL_CCFLAGS += ' -std=c99'
elif rtconfig.CROSS_TOOL == 'keil':
    LOCAL_CCFLAGS += ' --c99'

group = DefineGroup('fal', src, depend = ['PKG_USING_FAL'], CPPPATH = CPPPATH, LOCAL_CCFLAGS = LOCAL_CCFLAGS)

Return('group')

完成上面的修改後,我們新建的移植文件目錄ports下的移植文件就可以通過scons命令編譯進工程內了,下面開始修改ports\fal目錄下的移植文件。

  • FAL SFUD(W25Q128 Flash)移植

我們想使用FAL管理STM32L475片內Flash和W25Q128片外Flash,W25Q128 Flash的驅動由SFUD框架提供。FAL提供了SFUD的移植示例文件\fal-latest\samples\porting\fal_flash_sfud_port.c,該文件已經被複制到ports\fal目錄下,我們可以直接使用該文件。

FAL提供的SFUD移植示例中,SFUD框架向FAL提供的fal_flash_dev設備表項如下:

// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c

#ifndef FAL_USING_NOR_FLASH_DEV_NAME
#define FAL_USING_NOR_FLASH_DEV_NAME             "norflash0"
#endif

static sfud_flash_t sfud_dev = NULL;
struct fal_flash_dev nor_flash0 = {FAL_USING_NOR_FLASH_DEV_NAME, 0, 8 * 1024 * 1024, 4096, {init, read, write, erase}};


// projects\stm32l475_dfs_sample\rtconfig.h

/* system packages */
#define PKG_USING_FAL
#define FAL_DEBUG_CONFIG
#define FAL_DEBUG 1
#define FAL_PART_HAS_TABLE_CFG
#define FAL_USING_SFUD_PORT
#define FAL_USING_NOR_FLASH_DEV_NAME "W25Q128"
#define PKG_USING_FAL_LATEST_VERSION
#define PKG_FAL_VER_NUM 0x99999

在menuconfig FAL配置項中我們使用了SFUD並且將設備名修改爲了W25Q128,保存配置後在rtconfig.h中定義宏FAL_USING_NOR_FLASH_DEV_NAME的值爲"W25Q128",在fal_flash_sfud_port.c文件中是通過條件宏定義的,也即優先使用外界定義的FAL_USING_NOR_FLASH_DEV_NAME,這裏我們不需要修改。

SFUD提供的fal_flash_dev對象nor_flash0參數中,flash大小隻有8M字節,我們可以修改爲W25Q128的16M字節,也可以不修改,因爲在調用初始化接口函數init後,會從flash設備讀取正確的參數更新到nor_flash0表項中,我們在使用FAL組件前都需要調用FAL初始化函數fal_init,其內調用flash設備初始化函數fal_flash_init,最後會調用註冊到fal_flash_dev設備表項中的初始化函數device_table[i]->ops.init,所以nor_flash0表項參數會在FAL初始化時被更新。

這裏我們既然已經知道W25Q128 Flash的參數,便順手把參數修改如下:

// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c

struct fal_flash_dev nor_flash0 = {FAL_USING_NOR_FLASH_DEV_NAME, 0, 16 * 1024 * 1024, 4096, {init, read, write, erase}};

SFUD向FAL註冊的接口函數實際調用的是SFUD框架層的接口函數,調用過程如下:

// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c

static int init(void)
{

#ifdef RT_USING_SFUD
    /* RT-Thread RTOS platform */
    sfud_dev = rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME);
#else
    /* bare metal platform */
    extern sfud_flash sfud_norflash0;
    sfud_dev = &sfud_norflash0;
#endif

    if (NULL == sfud_dev)
    {
        return -1;
    }

    /* update the flash chip information */
    nor_flash0.blk_size = sfud_dev->chip.erase_gran;
    nor_flash0.len = sfud_dev->chip.capacity;

    return 0;
}

static int read(long offset, uint8_t *buf, size_t size)
{
    assert(sfud_dev);
    assert(sfud_dev->init_ok);
    sfud_read(sfud_dev, nor_flash0.addr + offset, size, buf);

    return size;
}

static int write(long offset, const uint8_t *buf, size_t size)
{
    assert(sfud_dev);
    assert(sfud_dev->init_ok);
    if (sfud_write(sfud_dev, nor_flash0.addr + offset, size, buf) != SFUD_SUCCESS)
    {
        return -1;
    }

    return size;
}

static int erase(long offset, size_t size)
{
    assert(sfud_dev);
    assert(sfud_dev->init_ok);
    if (sfud_erase(sfud_dev, nor_flash0.addr + offset, size) != SFUD_SUCCESS)
    {
        return -1;
    }

    return size;
}
  • FAL MCU Flash移植

STM32L475片內Flash驅動,RT-Thread已經在libraries\HAL_Drivers \drv_flash\drv_flash_l4.c目錄下提供了,同時還通過條件宏提供了向FAL註冊fal_flash_dev設備表項的代碼:

// projects\stm32l475_dfs_sample\board\board.h

#define STM32_FLASH_START_ADRESS       ((uint32_t)0x08000000)
#define STM32_FLASH_SIZE               (512 * 1024)
#define STM32_FLASH_END_ADDRESS        ((uint32_t)(STM32_FLASH_START_ADRESS + STM32_FLASH_SIZE))


// libraries\HAL_Drivers\drv_flash\drv_flash_l4.c

const struct fal_flash_dev stm32_onchip_flash = { "onchip_flash", STM32_FLASH_START_ADRESS, STM32_FLASH_SIZE, 2048, {NULL, fal_flash_read, fal_flash_write, fal_flash_erase} };

static int fal_flash_read(long offset, rt_uint8_t *buf, size_t size)
{
    return stm32_flash_read(stm32_onchip_flash.addr + offset, buf, size);
}

static int fal_flash_write(long offset, const rt_uint8_t *buf, size_t size)
{
    return stm32_flash_write(stm32_onchip_flash.addr + offset, buf, size);
}

static int fal_flash_erase(long offset, size_t size)
{
    return stm32_flash_erase(stm32_onchip_flash.addr + offset, size);
}

STM32L475向FAL提供的fal_flash_dev設備對象stm32_onchip_flash包含了STM32L475片內Flash的參數及其訪問接口函數,Flash參數在工程目錄的board.h頭文件中定義,Flash訪問接口函數則在驅動文件drv_flash_l4.c中提供,接口函數最終調用的是STM32L4 HAL庫函數,這裏就不展開介紹其過程了。

如果想使用STM32L475片內Flash驅動,需要定義相應的宏,這裏通過在工程目錄Kconfig文件中增加menuconfig配置來實現,新增的配置如下:

// projects\stm32l475_dfs_sample\board\Kconfig
menu "Hardware Drivers Config"

config SOC_STM32L475VE
    bool
    select SOC_SERIES_STM32L4
    default y
......
menu "On-chip Peripheral Drivers"
	......
    config BSP_USING_ON_CHIP_FLASH
        bool "Enable on-chip FLASH"
        default n
    ......

爲何增加宏BSP_USING_ON_CHIP_FLASH的配置項呢?主要是從工程編譯管理文件libraries\HAL_Drivers\SConscript中查得的,看驅動文件drv_flash/drv_flash_l4.c的編譯依賴宏是BSP_USING_ON_CHIP_FLASH與SOC_SERIES_STM32L4,後者在RT-Thread CPU架構與BSP移植過程時已經定義,所以這裏只需要在Kconfig文件中增加宏BSP_USING_ON_CHIP_FLASH的配置選項即可。

在Kconfig文件中新增宏BSP_USING_ON_CHIP_FLASH配置後保存,在工程目錄env環境輸入menuconfig開啓剛纔新增的配置選項,如下圖所示:
片內Flash啓用配置
STM32L475片內Flash到FAL的移植到這裏就完成了。

  • FAL分區表配置

在分區表配置文件ports\fal\fal_cfg.h中主要修改fal_flash_dev設備對象名,Flash設備名NOR_FLASH_DEV_NAME,FAL分區表FAL_PART_TABLE的定義等內容。

我們將onchip_flash分爲兩個分區,將W25Q128 Flash分爲五個分區,同時修改我們在底層flash驅動中提供的fal_flash_dev設備對象名和Flash設備名,修改後的FAL分區配置文件代碼如下:

// projects\stm32l475_dfs_sample\ports\fal\fal_cfg.h
......
#ifndef FAL_USING_NOR_FLASH_DEV_NAME
#define NOR_FLASH_DEV_NAME             "norflash0"
#else
#define NOR_FLASH_DEV_NAME              FAL_USING_NOR_FLASH_DEV_NAME
#endif

/* ===================== Flash device Configuration ========================= */
extern const struct fal_flash_dev stm32_onchip_flash;
extern struct fal_flash_dev nor_flash0;

/* flash device table */
#define FAL_FLASH_DEV_TABLE                                          \
{                                                                    \
    &stm32_onchip_flash,                                             \
    &nor_flash0,                                                     \
}
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE                                                                                                  \
{                                                                                                                       \
    {FAL_PART_MAGIC_WROD,        "app",     "onchip_flash",                                    0,       384 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD,      "param",     "onchip_flash",                           384 * 1024,       128 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD,  "easyflash", NOR_FLASH_DEV_NAME,                                    0,       512 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD,   "download", NOR_FLASH_DEV_NAME,                           512 * 1024,      1024 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD, "wifi_image", NOR_FLASH_DEV_NAME,                  (512 + 1024) * 1024,       512 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD,       "font", NOR_FLASH_DEV_NAME,            (512 + 1024 + 512) * 1024,  7 * 1024 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD, "filesystem", NOR_FLASH_DEV_NAME, (512 + 1024 + 512 + 7 * 1024) * 1024,  7 * 1024 * 1024, 0}, \
}
#endif /* FAL_PART_HAS_TABLE_CFG */

到這裏FAL移植就完成了,在使用FAL前需要對其進行初始化,也即調用調用函數fal_init,下面用一個示例程序驗證FAL軟件包移植是否成功。

1.4 FAL使用示例

我們主要想通過示例驗證FAL移植是否有問題,並熟悉FAL向上層提供的接口函數的使用,所以本示例先初始化FAL組件,然後對特定分區進行擦除、讀取、寫入等訪問操作,同時根據分區名獲取fal_partition分區參數及fal_flash_dev設備參數等信息。

在projects\stm32l475_dfs_sample\applications目錄下新建fal_sample.c文件,按照上面的實現目標在fal_sample.c文件中編寫實現代碼如下:

// projects\stm32l475_dfs_sample\applications\fal_sample.c

#include "rtthread.h"
#include "rtdevice.h"
#include "board.h"
#include "fal.h"


#define BUF_SIZE 1024

static int fal_test(const char *partiton_name)
{
    int ret;
    int i, j, len;
    uint8_t buf[BUF_SIZE];
    const struct fal_flash_dev *flash_dev = RT_NULL;
    const struct fal_partition *partition = RT_NULL;

    if (!partiton_name)
    {
        rt_kprintf("Input param partition name is null!\n");
        return -1;
    }

    partition = fal_partition_find(partiton_name);
    if (partition == RT_NULL)
    {
        rt_kprintf("Find partition (%s) failed!\n", partiton_name);
        ret = -1;
        return ret;
    }

    flash_dev = fal_flash_device_find(partition->flash_name);
    if (flash_dev == RT_NULL)
    {
        rt_kprintf("Find flash device (%s) failed!\n", partition->flash_name);
        ret = -1;
        return ret;
    }

    rt_kprintf("Flash device : %s   "
               "Flash size : %dK   \n"
               "Partition : %s   "
               "Partition size: %dK\n", 
                partition->flash_name, 
                flash_dev->len/1024,
                partition->name,
                partition->len/1024);

    /* erase all partition */
    ret = fal_partition_erase_all(partition);
    if (ret < 0)
    {
        rt_kprintf("Partition (%s) erase failed!\n", partition->name);
        ret = -1;
        return ret;
    }
    rt_kprintf("Erase (%s) partition finish!\n", partiton_name);

    /* read the specified partition and check data */
    for (i = 0; i < partition->len;)
    {
        rt_memset(buf, 0x00, BUF_SIZE);

        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        ret = fal_partition_read(partition, i, buf, len);
        if (ret < 0)
        {
            rt_kprintf("Partition (%s) read failed!\n", partition->name);
            ret = -1;
            return ret;
        }

        for(j = 0; j < len; j++)
        {
            if (buf[j] != 0xFF)
            {
                rt_kprintf("The erase operation did not really succeed!\n");
                ret = -1;
                return ret;
            }
        }
        i += len;
    }

    /* write 0x00 to the specified partition */
    for (i = 0; i < partition->len;)
    {
        rt_memset(buf, 0x00, BUF_SIZE);

        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        ret = fal_partition_write(partition, i, buf, len);
        if (ret < 0)
        {
            rt_kprintf("Partition (%s) write failed!\n", partition->name);
            ret = -1;
            return ret;
        }

        i += len;
    }
    rt_kprintf("Write (%s) partition finish! Write size %d(%dK).\n", partiton_name, i, i/1024);

    /* read the specified partition and check data */
    for (i = 0; i < partition->len;)
    {
        rt_memset(buf, 0xFF, BUF_SIZE);

        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        ret = fal_partition_read(partition, i, buf, len);
        if (ret < 0)
        {
            rt_kprintf("Partition (%s) read failed!\n", partition->name);
            ret = -1;
            return ret;
        }

        for(j = 0; j < len; j++)
        {
            if (buf[j] != 0x00)
            {
                rt_kprintf("The write operation did not really succeed!\n");
                ret = -1;
                return ret;
            }
        }

        i += len;
    }

    ret = 0;
    return ret;
}

static void fal_sample(void)
{
    /* 1- init */
    fal_init();

    if (fal_test("param") == 0)
    {
        rt_kprintf("Fal partition (%s) test success!\n", "param");
    }
    else
    {
        rt_kprintf("Fal partition (%s) test failed!\n", "param");
    }

    if (fal_test("download") == 0)
    {
        rt_kprintf("Fal partition (%s) test success!\n", "download");
    }
    else
    {
        rt_kprintf("Fal partition (%s) test failed!\n", "download");
    }
}

MSH_CMD_EXPORT(fal_sample, fal sample);

本示例程序是在前篇博客DFS文件系統管理與devfs/elmfat示例工程的基礎上新增的,由於之前已經編寫的源文件applications\dfs_sample.c中包含了QSPI設備綁定和SFUD Flash探測函數的自動調用,所以在本示例工程applications\fal_sample.c中並沒有進行QSPI與SFUD flash初始化和註冊工作,如果applications/dfs_sample.c源文件不存在,則需要在applications\fal_sample.c中完成QSPI與SFUD flash初始化及註冊工作。

在工程目錄啓動env環境並執行scons --target=mdk5命令,編譯生成MDK5工程文件,打開project.uvprojx編譯無錯誤(由於部分RT-Thread組件不支持Clang編譯器,把template.uvprojx工程默認編譯器設置爲ARM Compiler V5了),將程序燒錄到STM32L475潘多拉開發板中,運行結果如下:
FAL組件示例工程運行結果
在finsh環境下運行fal_sample結果正常,輸出到串口的不僅有FAL分區表信息,還有在特定分區中擦除、寫入、讀取數據的測試結果信息。

FAL爲便於用戶調試,也提供了finsh命令fal,包括fal probe / read / write / erase / bench等命令,命令使用示例如下:
finsh fal命令使用示例
本示例工程源碼下載地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample

二、DFS掛載到FAL分區示例

在前篇博客DFS文件系統管理與devfs/elmfat示例中我們將DFS框架中的elmfat文件系統掛載到了SFUD驅動的W25Q128塊設備上,這裏增加FAL flash抽象層,我們將elmfat文件系統掛載到W25Q128 flash設備的filesystem分區上,由於FAL管理的filesystem分區不是塊設備,需要先使用FAL分區轉BLK設備接口函數將filesystem分區轉換爲塊設備,然後再將DFS elmfat文件系統掛載到filesystem塊設備上。

掛載DFS elmfat文件系統的示例程序主要還是使用前篇博客elmfat_sample函數中的代碼,只是在前面增加了fal初始化和將分區filesystem創建爲塊設備的代碼。按照該目標在fal_sample.c文件中新增實現代碼如下:

// projects\stm32l475_dfs_sample\applications\fal_sample.c

#include "dfs_posix.h"

#define FS_PARTITION_NAME  "filesystem"

static void fal_elmfat_sample(void)
{
    int fd, size;
    struct statfs elm_stat;
    struct fal_blk_device *blk_dev;
    char str[] = "elmfat mount to W25Q flash.", buf[80];

    /* fal init */
    fal_init();

    /* create block device */
    blk_dev = (struct fal_blk_device *)fal_blk_device_create(FS_PARTITION_NAME);
    if(blk_dev == RT_NULL)
        rt_kprintf("Can't create a block device on '%s' partition.\n", FS_PARTITION_NAME);
    else
        rt_kprintf("Create a block device on the %s partition of flash successful.\n", FS_PARTITION_NAME);

    /* make a elmfat format filesystem */
    if(dfs_mkfs("elm", FS_PARTITION_NAME) == 0)
        rt_kprintf("make elmfat filesystem success.\n");

    /* mount elmfat file system to FS_PARTITION_NAME */
    if(dfs_mount(FS_PARTITION_NAME, "/", "elm", 0, 0) == 0)
        rt_kprintf("elmfat filesystem mount success.\n");

    /* Get elmfat file system statistics */
    if(statfs("/", &elm_stat) == 0)
        rt_kprintf("elmfat filesystem block size: %d, total blocks: %d, free blocks: %d.\n", 
                    elm_stat.f_bsize, elm_stat.f_blocks, elm_stat.f_bfree);

    if(mkdir("/user", 0x777) == 0)
        rt_kprintf("make a directory: '/user'.\n");

    rt_kprintf("Write string '%s' to /user/test.txt.\n", str);

    /* Open the file in create and read-write mode, create the file if it does not exist*/
    fd = open("/user/test.txt", O_WRONLY | O_CREAT);
    if (fd >= 0)
    {
        if(write(fd, str, sizeof(str)) == sizeof(str))
            rt_kprintf("Write data done.\n");

        close(fd);   
    }

    /* Open file in read-only mode */
    fd = open("/user/test.txt", O_RDONLY);
    if (fd >= 0)
    {
        size = read(fd, buf, sizeof(buf));

        close(fd);

        if(size == sizeof(str))
            rt_kprintf("Read data from file test.txt(size: %d): %s \n", size, buf);
    }
}
MSH_CMD_EXPORT_ALIAS(fal_elmfat_sample, fal_elmfat,fal elmfat sample);

使用MDK5編譯無報錯,將程序燒錄到STM32L475開發板中,程序運行結果如下:
在FAL上移植DFS elmfat文件系統示例運行結果
本示例工程源碼下載地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample

三、Easyflash移植到FAL分區示例

我們在使用linux或者windows系統時都有專門配置環境變量的地方,在使用RT-Thread時也有要保存類似環境變量這種鍵值關係的空間,特別是對於藍牙/WIFI等射頻類通信需要保存的配置項還不少。有些配置是在系統運行過程中產生的,自然不能保存到代碼區,由於這些配置在下次開機時仍需調用,也不能保存到SRAM內存區,只能保存到非易失性存儲區NVM(non-volatile memory)。

在系統運行過程中產生的配置項需要保存到NVM非易失性存儲區,也即ROM / Flash中,當然也可以使用DFS文件系統保存到某個配置文件中,但在文件中保存/獲取配置項的值並沒有那麼便利,RT-Thread提供了一個easyflash軟件包提供了專門的鍵值對(Key-Value)管理接口,可以讓用戶很方便的在NVM中通過接口函數保存/獲取配置項,而不需要關心該配置項的存儲位置。

3.1 Easyflash軟件包源碼獲取

在stm32l475_dfs_sample工程目錄打開env執行menuconfig命令,在RT-Thread online packages --> tools packages --> easyflash中啓用該軟件包,選擇lastest最新版本。

easyflash支持ENV環境變量、IAP在線升級、LOG日誌保存等功能,這裏我們只使用ENV環境變量功能,所以另外兩個保持默認的未選中狀態,easyflash配置界面如下:
easyflash啓用配置界面
保存配置,在env環境中執行pkgs --update命令,自動從github獲取easyflash軟件包到本地,獲取結果如下:
easyflash軟件包下載結果
下載的easyflash軟件包的目錄結構如下:
easyflash軟件包目錄結構N
在packages\EasyFlash-latest\docs\zh目錄下有詳細的說明文檔,包括api接口介紹、easyflash設計與實現原理、移植過程說明等,說明文檔比較詳細,這裏就不過多贅述了,只簡單介紹其初始化過程與移植過程。

3.2 easyflash環境變量管理

先看下easyflash管理環境變量ENV的數據結構描述:

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\inc\easyflash.h

typedef struct _ef_env {
    char *key;
    void *value;
    size_t value_len;
} ef_env, *ef_env_t;


// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\ports\ef_fal_port.c

/* default ENV set for user */
static const ef_env default_env_set[] = {
        {"iap_need_copy_app", "0"},
        {"iap_need_crc32_check", "0"},
        {"iap_copy_app_size", "0"},
        {"stop_in_bootloader", "0"},
};

用戶設置的環境變量都保存在default_env_set[]表中,用戶可以在編寫代碼時事先在該表中配置一部分環境變量,也可以在使用過程中通過接口函數往裏面新增、刪除、修改、獲取環境變量,easyflash移植有SFUD和FAL兩種方式,SFUD是直接在某個Flash上使用easyflash,FAL則是在某個分區上使用easyflash,我們只需要將環境變量保存在一段較小的flash分區中,因此使用FAL移植接口文件ef_fal_port.c。

接下來看easyflash軟件包初始化過程:

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\src\easyflash.c

/**
 * EasyFlash system initialize.
 *
 * @return result
 */
EfErrCode easyflash_init(void) {
    extern EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size);
    extern EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size);
    extern EfErrCode ef_iap_init(void);
    extern EfErrCode ef_log_init(void);

    size_t default_env_set_size = 0;
    const ef_env *default_env_set;
    EfErrCode result = EF_NO_ERR;

    result = ef_port_init(&default_env_set, &default_env_set_size);

#ifdef EF_USING_ENV
    if (result == EF_NO_ERR) {
        result = ef_env_init(default_env_set, default_env_set_size);
    }
#endif

#ifdef EF_USING_IAP
    if (result == EF_NO_ERR) {
        result = ef_iap_init();
    }
#endif

#ifdef EF_USING_LOG
    if (result == EF_NO_ERR) {
        result = ef_log_init();
    }
#endif

    if (result == EF_NO_ERR) {
        EF_INFO("EasyFlash V%s is initialize success.\n", EF_SW_VERSION);
    } else {
        EF_INFO("EasyFlash V%s is initialize fail.\n", EF_SW_VERSION);
    }
    EF_INFO("You can get the latest version on https://github.com/armink/EasyFlash .\n");

    return result;
}


// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\ports\ef_fal_port.c

/**
 * Flash port for hardware initialize.
 *
 * @param default_env default ENV set for user
 * @param default_env_size default ENV size
 *
 * @return result
 */
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {
    EfErrCode result = EF_NO_ERR;

    *default_env = default_env_set;
    *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);

    rt_sem_init(&env_cache_lock, "env lock", 1, RT_IPC_FLAG_PRIO);

    part = fal_partition_find(FAL_EF_PART_NAME);
    EF_ASSERT(part);

    return result;
}


// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\src\ef_env.c

/**
 * Flash ENV initialize.
 *
 * @param default_env default ENV set for user
 * @param default_env_size default ENV set size
 *
 * @return result
 */
EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size) {
    EfErrCode result = EF_NO_ERR;

#ifdef EF_ENV_USING_CACHE
    size_t i;
#endif

    EF_ASSERT(default_env);
    EF_ASSERT(ENV_AREA_SIZE);
    /* must be aligned with erase_min_size */
    EF_ASSERT(ENV_AREA_SIZE % EF_ERASE_MIN_SIZE == 0);
    /* sector number must be greater than or equal to 2 */
    EF_ASSERT(SECTOR_NUM >= 2);
    /* must be aligned with write granularity */
    EF_ASSERT((EF_STR_ENV_VALUE_MAX_SIZE * 8) % EF_WRITE_GRAN == 0);

    if (init_ok) {
        return EF_NO_ERR;
    }

#ifdef EF_ENV_USING_CACHE
    for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) {
        sector_cache_table[i].addr = FAILED_ADDR;
    }
    for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) {
        env_cache_table[i].addr = FAILED_ADDR;
    }
#endif /* EF_ENV_USING_CACHE */

    env_start_addr = EF_START_ADDR;
    default_env_set = default_env;
    default_env_set_size = default_env_size;

    EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE);

    result = ef_load_env();

#ifdef EF_ENV_AUTO_UPDATE
    if (result == EF_NO_ERR) {
        env_auto_update();
    }
#endif

    if (result == EF_NO_ERR) {
        init_ok = true;
    }

    return result;
}

easyflash初始化實際上主要是對環境變量表 default_env_set的相關處理,先是從移植文件ef_fal_port.c中獲取default_env_set的首地址與元素個數,包括獲取easyflash所使用FAL分區的結構體對象指針;然後配置ENV管理中需要使用的全局變量,最後將default_env_set表中配置的環境變量加載到SRAM內存中去,方便用戶程序對環境變量的使用與配置。

接下來看easyflash環境變量管理的常用接口函數聲明:

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\ports\ef_fal_port.c

#ifdef EF_USING_ENV
/* only supported on ef_env.c */
size_t ef_get_env_blob(const char *key, void *value_buf, size_t buf_len, size_t *saved_value_len);
EfErrCode ef_set_env_blob(const char *key, const void *value_buf, size_t buf_len);

/* ef_env.c, ef_env_legacy_wl.c and ef_env_legacy.c */
EfErrCode ef_load_env(void);
void ef_print_env(void);
char *ef_get_env(const char *key);
EfErrCode ef_set_env(const char *key, const char *value);
EfErrCode ef_del_env(const char *key);
EfErrCode ef_save_env(void);
EfErrCode ef_env_set_default(void);
size_t ef_get_env_write_bytes(void);
EfErrCode ef_set_and_save_env(const char *key, const char *value);
EfErrCode ef_del_and_save_env(const char *key);
#endif

更多接口函數說明可參閱EasyFlash-latest\docs\zh\api.md文檔或源碼。

3.3 easyflash移植

爲了方便後面easyflash軟件包的升級,以免我們的配置信息被覆蓋,依然採用FAL移植時的處理方式,在新建的ports文件夾下再新建EasyFlash文件夾用於保存其移植文件,同時把packages\EasyFlash-latest\ports\ef_fal_port.c複製一份到ports\EasyFlash\ef_fal_port.c中,把packages\EasyFlash-latest\SConscript複製一份到ports\EasyFlash\SConscript中,移植文件配置目錄如下:
easyflash移植文件目錄
在packages\EasyFlash-latest\SConscript文件中並沒有添加packages\EasyFlash-latest\ports目錄下的源碼文件,我們也不使用該目錄下的移植文件,所以該編譯配置文件不需要修改。

打開ports\EasyFlash\SConscript並修改源文件與頭文件的目錄配置,修改後的編譯配置腳本代碼如下:

// projects\stm32l475_dfs_sample\ports\EasyFlash\SConscript

from building import *

# get current directory
cwd     = GetCurrentDir()
# The set of source files associated with this SConscript file.
src     = []
src     += Glob('*.c')

path    = [cwd]

group = DefineGroup('EasyFlash', src, depend = ['PKG_USING_EASYFLASH'], CPPPATH = path)

Return('group')

接下來看移植文件ports\EasyFlash\ef_fal_port.c的修改,前面介紹的ef_port_init文件讀取環境變量配置表default_env_set的信息,並通過FAL分區名獲取分區對象指針,所以在ef_fal_port.c文件中需要修改環境變量配置表default_env_set和要存儲的FAL分區名FAL_EF_PART_NAME。

在前面介紹FAL時,專門在W25Q128 flash上配置了一個名爲easyflash的分區用於存儲easyflash軟件包管理的數據。環境變量我們先只設置一個開機次數,修改後的配置表default_env_set與分區名FAL_EF_PART_NAME如下:

// projects\stm32l475_dfs_sample\ports\EasyFlash\ef_fal_port.c

/* EasyFlash partition name on FAL partition table */
#define FAL_EF_PART_NAME               "easyflash"

/* default ENV set for user */
static const ef_env default_env_set[] = {
        {"boot_times", "0"}
};

easyflash要在FAL分區上保存/讀取環境變量,需要實現訪問FAL分區的函數,從與下面FAL抽象層的交互接口可以更熟悉移植過程,easyflash訪問FAL分區的函數實現如下:

// projects\stm32l475_dfs_sample\ports\EasyFlash\ef_fal_port.c

static const struct fal_partition *part = NULL;

/**
 * Flash port for hardware initialize.
 *
 * @param default_env default ENV set for user
 * @param default_env_size default ENV size
 *
 * @return result
 */
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {
    EfErrCode result = EF_NO_ERR;

    *default_env = default_env_set;
    *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);

    rt_sem_init(&env_cache_lock, "env lock", 1, RT_IPC_FLAG_PRIO);

    part = fal_partition_find(FAL_EF_PART_NAME);
    EF_ASSERT(part);

    return result;
}

/**
 * Read data from flash.
 * @note This operation's units is word.
 *
 * @param addr flash address
 * @param buf buffer to store read data
 * @param size read bytes size
 *
 * @return result
 */
EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size) {
    EfErrCode result = EF_NO_ERR;

    fal_partition_read(part, addr, (uint8_t *)buf, size);

    return result;
}

/**
 * Erase data on flash.
 * @note This operation is irreversible.
 * @note This operation's units is different which on many chips.
 *
 * @param addr flash address
 * @param size erase bytes size
 *
 * @return result
 */
EfErrCode ef_port_erase(uint32_t addr, size_t size) {
    EfErrCode result = EF_NO_ERR;

    /* make sure the start address is a multiple of FLASH_ERASE_MIN_SIZE */
    EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);

    if (fal_partition_erase(part, addr, size) < 0)
    {
        result = EF_ERASE_ERR;
    }

    return result;
}
/**
 * Write data to flash.
 * @note This operation's units is word.
 * @note This operation must after erase. @see flash_erase.
 *
 * @param addr flash address
 * @param buf the write data buffer
 * @param size write bytes size
 *
 * @return result
 */
EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size) {
    EfErrCode result = EF_NO_ERR;

    if (fal_partition_write(part, addr, (uint8_t *)buf, size) < 0)
    {
        result = EF_WRITE_ERR;
    }

    return result;
}

到這裏easyflash組件的移植完成了,下面用一個示例程序驗證移植是否成功。

3.4 easyflash使用示例

在使用easyflash組件前需要先調用easyflash_init進行初始化,在示例程序中我們獲取環境變量boot_times的值,然後對其執行加一操作,串口打印當前boot_times的值後,再把新的環境變量值設置給變量名boot_times,最後將該環境變量的新值保存到FAL指定分區中。

需要注意的是獲取的環境變量值爲字符串格式,如果要對其進行數字運算,需要先將該值轉換爲數字類型,同樣保存該環境變量時也是以字符串形式設置的,通過easyflash提供的接口函數參數格式也可以看出來。

按照上面的任務目標,在fal_sample.c文件中新增easyflash示例代碼如下:

// projects\stm32l475_dfs_sample\applications\fal_sample.c

#include "easyflash.h"
#include <stdlib.h>

static void easyflash_sample(void)
{
    /* fal init */
    fal_init();

    /* easyflash init */
    if(easyflash_init() == EF_NO_ERR)
    {
        uint32_t i_boot_times = NULL;
        char *c_old_boot_times, c_new_boot_times[11] = {0};

        /* get the boot count number from Env */
        c_old_boot_times = ef_get_env("boot_times");
        /* get the boot count number failed */
        if (c_old_boot_times == RT_NULL)
            c_old_boot_times[0] = '0';

        i_boot_times = atol(c_old_boot_times);
        /* boot count +1 */
        i_boot_times ++;
        rt_kprintf("===============================================\n");
        rt_kprintf("The system now boot %d times\n", i_boot_times);
        rt_kprintf("===============================================\n");
        /* interger to string */
        sprintf(c_new_boot_times, "%d", i_boot_times);
        /* set and store the boot count number to Env */
        ef_set_env("boot_times", c_new_boot_times);
        ef_save_env();
    }
}
MSH_CMD_EXPORT(easyflash_sample, easyflash sample);

在工程目錄打開env執行scons --target=mdk5命令,自動編譯生成MDK5工程文件,打開project.uvprojx,編譯無報錯,將程序燒錄到STM32L475開發板中,運行結果如下:
easyflash示例運行結果
easyflash爲方便調試,也提供了finsh命令printenv / resetenv / setenv / saveenv / getvalue等,這些命令的使用示例如下:
easyflash finsh命令使用示例
本示例工程源碼下載地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample

更多文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章