RT-Thread进阶之文件系统

点击“蓝字”关注我们吧

1.引入

1.1 文件系统引入

在早期的嵌入式系统中,需要存储的数据比较少,数据类型也比较单一,往往使用直接在存储设备中的指定地址写入数据的方法来存储数据。然而随着嵌入式设备功能的发展,需要存储的数据越来越多,也越来越复杂,这时仍使用旧方法来存储并管理数据就变得非常繁琐困难。因此我们需要新的数据管理方式来简化存储数据的组织形式,这就是文件系统的由来。

1.2 虚拟文件系统引入

为了统一众多不同类型的文件系统,虚拟文件系统对实际文件系统进行抽象,采用统一的文件系统向用户提供相应的一组统一的标准的文件操作接口(open,read,close,select,poll等)。

2 DFS 简介

DFS( Device File System)是一种抽象的文件机制,RT-Thread中对文件系统的相关操作实际上都是通过操作DFS实现,也就是说DFS是对各种文件系统的抽象。DFS使的其他部分无须关心不同文件系统之间的差异,使得RT-Thread可以支持多种类型的文件系统。

3 DFS 框架

RT-Thread DFS 组件的主要功能特点有:

  • 为应用程序提供统一的 POSIX 文件和目录操作接口:read、write、poll/select 等。

  • 支持多种类型的文件系统,如 FatFS、RomFS、DevFS 等,并提供普通文件、设备文件、网络文件描述符的管理。

  • 支持多种类型的存储设备,如 SD Card、SPI Flash、Nand Flash 等。

DFS 的层次架构如下图所示,主要分为 POSIX 接口层、虚拟文件系统层和设备抽象层。


POSIX 接口层:
POSIX 表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写 POSIX),POSIX 标准定义了操作系统应该为应用程序提供的接口标准,是 IEEE 为要在各种 UNIX 操作系统上运行的软件而定义的一系列 API 标准的总称。


虚拟文件系统层:
用户可以将具体的文件系统注册到 DFS 中,如 FatFS、RomFS、DevFS 等。


设备抽象层:

设备抽象层将物理设备如 SD Card、SPI Flash、Nand Flash,抽象成符合文件系统能够访问的设备,例如 FAT 文件系统要求存储设备必须是块设备类型。

不同文件系统类型是独立于存储设备驱动而实现的,因此把底层存储设备的驱动接口和文件系统对接起来之后,才可以正确地使用文件系统功能。

4 DFS 数据结构

文件系统操作表:

1const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];

文件系统表:

1struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX];

文件描述符:

1static struct dfs_fdtable _fdtab;

5 虚拟文件系统使用步骤

  1. 初始化 DFS 组件。

  2. 注册具体类型的文件系统。

  3. 挂载文件系统

  4. 当文件系统不再使用,可以将它卸载。

5.1 初始化 DFS 组件

dfs_init() 函数会初始化 DFS 所需的相关资源,创建一些关键的数据结构, 有了这些数据结构,DFS 便能在系统中找到特定的文件系统,并获得对特定存储设备内文件的操作方法。

dfs_init()加入了自动初始化机制,在系统上电后会自动运行dfs_init()。

1INIT_PREV_EXPORT(dfs_init);

实例化DFS 组件相关的数据结构

1const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];
1struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX]
2
1static struct dfs_fdtable _fdtab
2

创建当前目录表

1char working_directory[DFS_PATH_MAX] = {"/"}

初始化 DFS:

  • 清除文件系统操作表

  • 清除文件系统表

  • 清除文件描述符表

  • 初始化互斥量

  • 设置当前工作目录为“/”

5.2 注册具体类型的文件系统

在 DFS 组件初始化之后,还需要初始化使用的具体类型的文件系统,也就是将具体类型的文件系统注册到 DFS 中。注册文件系统的接口如下所示:

1int dfs_register(const struct dfs_filesystem_ops *ops);
  • 检查这个文件系统是否已经存在于文件系统操作表的目录中

  • 在文件系统操作表中找出一个空的文件类型条目

  • 将这个文件系统的数据结构地址赋值给空的文件系统操作表目录

5.3 挂载文件系统

在挂载文件系统之前,如果是用作存储设备,还需要先存储设备注册为块设备,然后格式化成对应的文件系统后,才能挂载。

在 RT-Thread 中,挂载是指将一个存储设备挂接到一个已存在的路径上。我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的路径上,然后通过这个路径来访问存储设备。挂载文件系统的接口如下所示:

1int dfs_mount(const char   *device_name,
2              const char   *path,
3              const char   *filesystemtype,
4              unsigned long rwflag,
5              const void   *data);
  • 在文件系统操作表中找出特定的文件系统

  • 为特殊文件系统建立完整路径

  • 检查路径是否存在

  • 检查文件系统是否挂载在文件系统表中

  • 检查文件系统表是否有空余,如果有,把空余地址指向给此文件系统

  • 注册文件系统

  • 调用此文件系统的挂载接口

5.4 卸载文件系统

当某个文件系统不需要再使用了,那么可以将它卸载掉。卸载文件系统的接口如下所示:

1int dfs_unmount(const char *specialfile);
  • 检查路径是否存在

  • 在文件系统表中找到此文件系统

  • 清除文件系统表的这个条目内容

  • 调用此文件系统的卸载接口

6 文件系统实例演示

6.1 devfs文件系统

6.1.1 简介

devfs是设备文件系统,设备文件系统是用来把一切设备都抽象为像文件那样操作(如可读,可写)。

devfs默认挂载在"/dev"路径下。但是会发现在根目录下使用shell 的ls命令不能看到“/dev”目录, 但是cd "/dev" 能进入dev 目录 ,并且进入dev目录后也能显示dev下的设备。

因为根目录“/"下并没有创建任何文件夹,所以在根目录下ls命令自然看不到“/dev”目录。由于挂载文件系统需要挂载在一个已存在的路径上,devfs属于特殊文件系统,DFS为devfs设备文件系统注册时设置了专门的“/dev”路径以供设备文件系统挂载。

 1   /* Check if the path exists or not, raw APIs call, fixme */
 2    if ((strcmp(fullpath, "/") != 0) && (strcmp(fullpath, "/dev") != 0))
 3    {
 4        struct dfs_fd fd;
 5
 6        if (dfs_file_open(&fd, fullpath, O_RDONLY | O_DIRECTORY) < 0)
 7        {
 8            rt_free(fullpath);
 9            rt_set_errno(-ENOTDIR);
10
11            return -1;
12        }
13        dfs_file_close(&fd);
14    }
6.1.2 注册devfs文件系统

如果开启了DFS,devfs设备文件系统会在dfs_init()里自动初始化和挂载。

 1#ifdef RT_USING_DFS_DEVFS
 2    {
 3        extern int devfs_init(void);
 4
 5        /* if enable devfs, initialize and mount it as soon as possible */
 6        devfs_init();
 7
 8        dfs_mount(NULL, "/dev", "devfs", 0, 0);
 9    }
10#endif

设置devfs文件系统的数据结构:_device_fs

 1static const struct dfs_filesystem_ops _device_fs =
 2{
 3    "devfs",
 4    DFS_FS_FLAG_DEFAULT,
 5    &_device_fops,
 6
 7    dfs_device_fs_mount,
 8    RT_NULL,
 9    RT_NULL,
10    RT_NULL,
11
12    RT_NULL,
13    dfs_device_fs_stat,
14    RT_NULL,
15};

将devfs文件系统的数据结构挂载到文件系统操作表里

1int devfs_init(void)
2{
3    /* register rom file system */
4    dfs_register(&_device_fs);
5
6    return 0;
7}
  • 检查devfs文件系统是否已经存在于文件系统操作表的目录中

  • 在文件系统操作表中找出一个空的文件类型条目

  • 将devfs文件系统的数据结构_device_fs地址赋值给空的文件系统操作表目录

文件系统操作表filesystem_operation_table的第一个目录:

6.1.2 挂载devfs文件系统
1dfs_mount(NULL, "/dev", "devfs", 0, 0)
  • 在文件系统操作表中找出devfs文件系统

  • 跳过检查"/dev"路径是否存在

  • 检查devfs文件系统是否已经挂载在文件系统表中

  • 检查文件系统表是否有空余,如果有,把空余地址赋值给devfs文件系统

  • 注册文件系统

  • 调用devfs文件系统的挂载接口

文件系统表filesystem_table的第一个目录:

6.1.3 测试devfs文件系统

在根目录下使用shell 的cd命令切换到/dev目录,然后使用ls命令:


6.2 RomFS文件系统

6.2.1 简介

RomFS是在嵌入式设备上常用的一种文件系统,具备体积小,可靠性高,读取速度快等优点,常用来作为系统初始文件系统。但也具有其局限性,RomFS是一种只读文件系统。

6.2.2 注册devfs文件系统

把RomFS当作初始文件系统rootfs挂载在根目录,在RomFS里创建几个目录,用于其他文件系统的挂载点。

更改…\romfs.c文件,添加mnt文件夹和user文件夹。

 1#include <rtthread.h>
 2#include <dfs_romfs.h>
 3
 4static const struct romfs_dirent _romfs_root[] = {
 5    {ROMFS_DIRENT_DIR, "mnt", RT_NULL, 0},
 6    {ROMFS_DIRENT_DIR, "user", RT_NULL, 0}
 7};
 8
 9const struct romfs_dirent romfs_root = {
10    ROMFS_DIRENT_DIR, "/", (rt_uint8_t *)_romfs_root, sizeof(_romfs_root)/sizeof(_romfs_root[0])
11};

设置romfs文件系统的数据结构:_romfs

 1static const struct dfs_filesystem_ops _romfs =
 2{
 3    "rom",
 4    DFS_FS_FLAG_DEFAULT,
 5    &_rom_fops,
 6
 7    dfs_romfs_mount,
 8    dfs_romfs_unmount,
 9    NULL,
10    NULL,
11
12    NULL,
13    dfs_romfs_stat,
14    NULL,
15};

将romfs文件系统的数据结构挂载到文件系统操作表里

1int dfs_romfs_init(void)
2{
3    /* register rom file system */
4    dfs_register(&_romfs);
5    return 0;
6}
7INIT_COMPONENT_EXPORT(dfs_romfs_init);//自动初始化
  • 检查romfs文件系统是否已经存在于文件系统操作表的目录中

  • 在文件系统操作表中找出一个空的文件类型条目

  • 将romfs文件系统的数据结构_romfs地址赋值给空的文件系统操作表目录

文件系统操作表filesystem_operation_table的第二个目录:

6.2.3 挂载romfs文件系统
 1int mnt_init(void)
 2{
 3   if(dfs_mount (RT_NULL,"/","rom",0,&(romfs_root)) == 0)
 4   {
 5       rt_kprintf("ROM file system initializated;\n");
 6   }
 7   else
 8   {
 9        rt_kprintf("ROM file system initializate failed;\n");
10   }
11   return 0;
12}
13INIT_ENV_EXPORT(mnt_init);
  • 在文件系统操作表中找出romfs文件系统

  • 检查"/"路径是否存在

  • 检查romfs文件系统是否已经挂载在文件系统表中

  • 检查文件系统表是否有空余,如果有,把空余地址指向romfs文件系统

  • 注册文件系统

  • 调用romfs文件系统的挂载接口


文件系统表filesystem_table的第二个目录:

6.2.4 测试RomFS文件系统

在根目录下使用shell 的ls命令:

6.3 RamFS文件系统

6.3.1 简介

RamFS是内存文件系统,它不能格式化,可以同时创建多个,在创建时可以指定其最大能使用的内存大小。其优点是读写速度很快,但存在掉电丢失的风险。如果一个进程的性能瓶颈是硬盘的读写,那么可以考虑在RamFS上进行大文件的读写操作。

RT-Thread的RamFS设计之初未考虑支持文件夹,所以不能使用mkdir。

6.3.2 注册RamFS文件系统

设置ramfs文件系统的数据结构:_ramfs

 1static const struct dfs_filesystem_ops _ramfs =
 2{
 3    "ram",
 4    DFS_FS_FLAG_DEFAULT,
 5    &_ram_fops,
 6
 7    dfs_ramfs_mount,
 8    dfs_ramfs_unmount,
 9    NULL, /* mkfs */
10    dfs_ramfs_statfs,
11
12    dfs_ramfs_unlink,
13    dfs_ramfs_stat,
14    dfs_ramfs_rename,
15};

将ramfs文件系统的数据结构挂载到文件系统操作表里

1int dfs_ramfs_init(void)
2{
3    /* register ram file system */
4    dfs_register(&_ramfs);
5
6    return 0;
7}
8INIT_COMPONENT_EXPORT(dfs_ramfs_init);
  • 检查ramfs文件系统是否已经存在于文件系统操作表的目录中

  • 在文件系统操作表中找出一个空的文件类型条目

  • 将ramfs文件系统的数据结构_ramfs地址赋值给空的文件系统操作表目录

文件系统操作表filesystem_operation_table的第三个目录:

6.3.3 挂载RamFS文件系统
 1int mnt_ram_elminit(void)
 2{
 3   if(dfs_mount (RT_NULL,"/mnt","ram",0,dfs_ramfs_create(rampool, 1024)) == 0)
 4   {
 5       rt_kprintf("ram file system initializated;\n");
 6   }
 7   else
 8   {
 9        rt_kprintf("ram file system initializate failed;\n");
10   }
11   return 0;
12}
13INIT_ENV_EXPORT(mnt_ram_elminit);
  • 在文件系统操作表中找出ramfs文件系统

  • 检查"/mnt"路径是否存在

  • 检查ramfs文件系统是否已经挂载在文件系统表中

  • 检查文件系统表是否有空余,如果有,把空余地址指向ramfs文件系统

  • 注册文件系统

  • 调用ramfs文件系统的挂载接口

文件系统表filesystem_table的第三个目录:

6.3.4 测试RamFS文件系统

在根目录下使用shell 的cd命令切换到/mnt目录,然后使用ls命令:

6.4 elm-FAT文件系统

6.4.1 简介

FatFs 是一个通用的文件系统(FAT/exFAT)模块,用于在小型嵌入式系统中实现FAT文件系统。

6.4.2 使用流程
  • 初始化 DFS 组件。

  • 初始化具体类型的文件系统。

  • 在存储器上创建块设备。

  • 格式化块设备。

  • 挂载块设备到 DFS 目录中。

  • 当文件系统不再使用,可以将它卸载

6.4.3 注册elm-FAT文件系统

设置fatfs文件系统的数据结构:dfs_elm

 1static const struct dfs_filesystem_ops dfs_elm =
 2{
 3    "elm",
 4    DFS_FS_FLAG_DEFAULT,
 5    &dfs_elm_fops,
 6
 7    dfs_elm_mount,
 8    dfs_elm_unmount,
 9    dfs_elm_mkfs,
10    dfs_elm_statfs,
11
12    dfs_elm_unlink,
13    dfs_elm_stat,
14    dfs_elm_rename,
15};

将fatfs文件系统的数据结构挂载到文件系统操作表里

1int elm_init(void)
2{
3    /* register fatfs file system */
4    dfs_register(&dfs_elm);
5
6    return 0;
7}
8INIT_COMPONENT_EXPORT(elm_init);
  • 检查fatfs文件系统是否已经存在于文件系统操作表的目录中

  • 在文件系统操作表中找出一个空的文件类型条目

  • 将fatfs文件系统的数据结构dfs_elm 地址赋值给空的文件系统操作表目录

elm-FAT文件系统注册过程如下图所示:

6.4.4 挂载elm-FAT文件系统
 1void sd_mount(void *parameter)
 2{
 3    while (1)
 4    {
 5        rt_thread_mdelay(500);
 6        if(rt_device_find("sd0") != RT_NULL)
 7        {
 8            if (dfs_mount("sd0", "/fatfs", "elm", 0, 0) == RT_EOK)
 9            {
10                LOG_I("sd card mount to '/fatfs'");
11                break;
12            }
13            else
14            {
15                LOG_W("sd card mount to '/fatfs' failed!");
16            }
17        }
18    }
19}
20
21int stm32_sdcard_mount(void)
22{
23    rt_thread_t tid;
24
25    tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
26                           1024, RT_THREAD_PRIORITY_MAX - 2, 20);
27    if (tid != RT_NULL)
28    {
29        rt_thread_startup(tid);
30    }
31    else
32    {
33        LOG_E("create sd_mount thread err!");
34    }
35    return RT_EOK;
36}
37INIT_APP_EXPORT(stm32_sdcard_mount);
  • 在文件系统操作表中找出elm文件系统

  • 检查"/fatfs"路径是否存在

  • 检查elm文件系统是否已经挂载在文件系统表中

  • 检查文件系统表是否有空余,如果有,把空余地址指向elm文件系统

  • 注册文件系统

  • 调用elm文件系统的挂载接口

6.4.5 测试elm-FAT文件系统

在根目录下使用shell 的cd命令切换到/fatfs目录,然后使用ls命令:

6.5 littlefs文件系统

6.5.1 简介

littlefs 是 ARM 官方推出的,专为嵌入式系统设计的文件系统,相比传统的文件系统,littlefs 具有以下优点:

  • 自带擦写均衡

  • 支持掉电保护

  • 占用的 RAM/ROM 少

littlefs 自带的擦写均衡和掉电保护使开发者可以放心的将文件系统挂载到 nor flash 上。

层级关系
littlefs 在 RT-Thread 上运行的层级关系图如下所示:

6.5.2 使用流程
  • 初始化 DFS 组件。

  • 使能 littlefs 软件包。

  • 使能 MTD 设备。

  • 使能 fal,用来创建 MTD 设备。

  • 创建 MTD 设备

  • 挂载MTD设备到 DFS 目录中。

6.5.3 注册littlefs文件系统

设置littlefs文件系统的数据结构:_dfs_lfs_ops

 1static const struct dfs_filesystem_ops _dfs_lfs_ops = {
 2    "lfs",
 3    DFS_FS_FLAG_DEFAULT,
 4    &_dfs_lfs_fops,
 5
 6    _dfs_lfs_mount,
 7    _dfs_lfs_unmount,
 8
 9    _dfs_lfs_mkfs,
10    _dfs_lfs_statfs,
11    _dfs_lfs_unlink,
12    _dfs_lfs_stat,
13    _dfs_lfs_rename,
14};

将littlefs文件系统的数据结构挂载到文件系统操作表里

1int dfs_lfs_init(void)
2{
3    /* init file system lock */
4    rt_mutex_init(&_lfs_lock, "lfsmtx", RT_IPC_FLAG_FIFO);
5    /* register ram file system */
6    return dfs_register(&_dfs_lfs_ops);
7}
8INIT_COMPONENT_EXPORT(dfs_lfs_init);
  • 检查littlefs文件系统是否已经存在于文件系统操作表的目录中

  • 在文件系统操作表中找出一个空的文件类型条目

  • 将littlefs文件系统的数据结构_dfs_lfs_ops 地址赋值给空的文件系统操作表目录

6.5.4 挂载littlefs文件系统
 1    ...
 2    struct rt_device *mtd_dev = RT_NULL;
 3
 4    ...
 5    /* 初始化 fal */    
 6    fal_init();
 7    /* 生成 mtd 设备 */
 8    mtd_dev = fal_mtd_nor_device_create(FS_PARTITION_NAME);
 9    if (!mtd_dev)
10    {
11        LOG_E("Can't create a mtd device on '%s' partition.", FS_PARTITION_NAME);
12    }
13    else
14    {
15        /* 挂载 littlefs */
16        if (dfs_mount(FS_PARTITION_NAME, "/littlefs", "lfs", 0, 0) == 0)
17        {
18            LOG_I("Filesystem initialized!");
19        }
20        else
21        {
22            /* 格式化文件系统 */
23            dfs_mkfs("lfs", FS_PARTITION_NAME);
24            /* 挂载 littlefs */
25            if (dfs_mount("filesystem", "/littlefs", "lfs", 0, 0) == 0)
26            {
27                LOG_I("Filesystem initialized!");
28            }
29            else
30            {
31                LOG_E("Failed to initialize filesystem!");
32            }
33        }
34    }
35    ...
  • 在文件系统操作表中找出lfs文件系统

  • 检查"/littlefs"路径是否存在

  • 检查littlefs文件系统是否已经挂载在文件系统表中

  • 检查文件系统表是否有空余,如果有,把空余地址指向littlefs文件系统

  • 注册文件系统

  • 调用littlefs文件系统的挂载接口

6.5.5 测试littlefs文件系统

在根目录下使用shell 的cd命令切换到/littlefs目录,然后使用ls命令:


注意:spi_flash.h中缺少一个头文件,需要自行添加

6.6 文件系统综合例程

DevFS、RomFS、RamFS、FatFS文件系统配置:



littlefs文件系统配置;



主程序:

  1#include <rtthread.h>
  2#include <rtdevice.h>
  3#include <board.h>
  4#include <fal.h>
  5
  6#include <dfs_fs.h>
  7#include <dfs_romfs.h>
  8#include <dfs_ramfs.h>
  9#include <dfs_posix.h>
 10
 11#define DBG_TAG "main"
 12#define DBG_LVL DBG_LOG
 13#include <rtdbg.h>
 14
 15/* defined the LED0 pin: PH10 */
 16#define LED0_PIN    GET_PIN(H, 10)
 17
 18int main(void)
 19{
 20    int count = 1;
 21    rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
 22
 23    while (count++)
 24    {
 25        rt_pin_write(LED0_PIN, PIN_HIGH);
 26        rt_thread_mdelay(1000);
 27        rt_pin_write(LED0_PIN, PIN_LOW);
 28        rt_thread_mdelay(1000);
 29    }
 30
 31    return RT_EOK;
 32}
 33rt_uint8_t rampool[128];
 34
 35void sd_mount(void *parameter)
 36{
 37    while (1)
 38    {
 39        rt_thread_mdelay(500);
 40        if(rt_device_find("sd0") != RT_NULL)
 41        {
 42            if (dfs_mount("sd0", "/fatfs", "elm", 0, 0) == RT_EOK)
 43            {
 44                LOG_I("sd card mount to '/fatfs'");
 45                break;
 46            }
 47            else
 48            {
 49                LOG_W("sd card mount to '/fatfs' failed!");
 50            }
 51        }
 52    }
 53}
 54
 55int fs_init(void)
 56{
 57    /* partition initialized */
 58    fal_init();
 59
 60    if(dfs_mount (RT_NULL,"/","rom",0,&(romfs_root)) == 0)
 61    {
 62        LOG_I("ROM file system initializated;\n");
 63    }
 64    else
 65    {
 66        LOG_I("ROM file system initializate failed;\n");
 67    }
 68
 69    if(dfs_mount (RT_NULL,"/ram","ram",0,dfs_ramfs_create(rampool, sizeof(rampool))) == 0)
 70    {
 71        LOG_I("ram file system initializated;\n");
 72    }
 73    else
 74    {
 75        LOG_I("ram file system initializate failed;\n");
 76    }
 77
 78    /* Create a block device on the file system partition of spi flash */
 79    struct rt_device *flash_dev = fal_mtd_nor_device_create("filesystem");
 80
 81    if (flash_dev == RT_NULL)
 82    {
 83        LOG_I("Can't create a mtd device on '%s' partition.", "filesystem");
 84    }
 85    else
 86    {
 87        LOG_I("Create a mtd device on the %s partition of flash successful.", "filesystem");
 88    }
 89    /* mount the file system from "filesystem" partition of spi flash. */
 90    if (dfs_mount(flash_dev->parent.name, "/littlefs", "lfs", 0, 0) == 0)
 91    {
 92        LOG_I("littlefs initialized!");
 93    }
 94    else
 95    {
 96        dfs_mkfs("lfs", flash_dev->parent.name);
 97        if (dfs_mount(flash_dev->parent.name, "/", "lfs", 0, 0) == 0)
 98        {
 99            LOG_I("littlefs initialized!");
100        }
101    }
102
103    rt_thread_t tid;
104
105    tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
106                           1024, RT_THREAD_PRIORITY_MAX - 2, 20);
107    if (tid != RT_NULL)
108    {
109        rt_thread_startup(tid);
110    }
111    else
112    {
113        LOG_E("create sd_mount thread err!");
114    }
115    return 0;
116}
117INIT_COMPONENT_EXPORT(fs_init);

测试:


工程地址:

https://gitee.com/Aladdin-Wang/RT-FOTA-STM32L431/tree/master/stm32f429_app

你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群!

RT-Thread


让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。

长按二维码,关注我们

点击阅读原文,进入RT-Thread GitHub 

点个“在看”,让知识流行起来

“在看”的小可爱永远十八岁!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值