基于 RT-Thread 的智能家居 DIY 教程连载(3)——文件系统灵活用

智能家居 DIY 教程连载(3)

文件系统 so easy

Hi,各位小伙伴,DIY 活动已经来到了第三周!前两周的任务大家都完成了吗?本周将会迎来新的挑战——文件系统。本文将从 SPI Flash 和 SD Card 两方面给大家讲解如何使用文件系统,以及针对本次 DIY 做出的一些优化,会大大增强系统性能,一起来看看吧~

本文目录

    1. 第三周任务回顾
    1. RT-Thread 文件系统简要介绍
    1. 在 SPI Flash 上使用文件系统
    1. 在 SD Card 上使用文件系统
    1. 针对本次 DIY 的一些系统性能优化(重点
    1. 开源代码
    1. 注意事项

1. 第三周任务概览

我们来回顾一下第三周的任务:

  • 了解 RT-Thread 文件系统,在接收节点中使用文件系统,存放来自发送节点发送过来的数据

上述任务比较单一,只是文件系统而已。不过,能巧妙灵活的把文件系统用好用对,可不是一件轻松的事情。

2. RT-Thread 文件系统简要介绍

DFS 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统,文件系统的名称使用类似 UNIX 文件、文件夹的风格。

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

  • 为应用程序提供统一的 POSIX 文件和目录操作接口:read、write、poll/select 等。
  • 支持多种类型的文件系统,如 FatFS、RomFS、DevFS 等,并提供普通文件、设备文件、网络文件描述符的管理。
  • 支持多种类型的存储设备,如 SD Card、SPI Flash、Nand Flash 等。

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

在这里插入图片描述
DFS 的更多内容,请在 RT-Thread 文档中心中查看,亦可点击本链接跳转。这里不过多赘述。

3. 在 SPI Flash 上使用文件系统

3.1 准备工作

以正点原子的潘多拉开发板 (Iot Board) 为例,教大家在 SPI Flash 上使用文件系统。

值得一提的是,RT-Thread 已经将 libc 那套文件系统接口对接到 DSF 上了,在 env 工具中开启 libc 和 DFS 即可,本次教程使用 libc 的那套接口进行文件的打开/关闭、读取/写入。

在 menuconfig 中开启 libc:

RT-Thread Components  --->
    POSIX layer and C standard library  --->
        [*] Enable libc APIs from toolchain

在 meunconfig 中开启 DFS,本教程使用 elmfatfs 文件系统,需要将 elmfatfs 挂载到 RT-Thread 的 DFS 上,所以 elmfatfs 也要开启:

RT-Thread Components  --->
    Device virtual file system  --->
        [*] Using device virtual file system
        [*]   Enable elm-chan fatfs

当然,不要忘记在 meunconfig 中开启 SPI Flash:

Hardware Drivers Config  --->
    Onboard Peripheral Drivers  --->
        [*] Enable QSPI FLASH (W25Q128 qspi1)

潘多拉开发板上的 SPI Flash 使用的是 QSPI 接口,还需要在 meunconfig 中把 QSPI 接口开启:

Hardware Drivers Config  --->
    On-chip Peripheral Drivers  --->
        [*] Enable QSPI BUS

退出 menuconfig 后需要输入 “scons --target=mdk5” 更新工程。

3.2 文件系统的挂载

本次 DIY 使用的文件系统是 elmfatfs,elmfatfs 需要在块设备上才能进行文件操作。潘多拉板子上的 SPI Flash 是 W25Q128,我们需要将 W25Q128 注册成块设备,才能使用 elmfatfs 进行文件操作。如下示例代码:

static int rt_hw_qspi_flash_with_sfud_init(void)
{
    stm32_qspi_bus_attach_device("qspi1", "qspi10", RT_NULL, 4, w25qxx_enter_qspi_mode, RT_NULL);
    if (RT_NULL == rt_sfud_flash_probe("W25Q128", "qspi10"))
        return -RT_ERROR;
    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_qspi_flash_with_sfud_init);

在 FinSH 中输入 “list_decive”,即可看到 W25Q128 注册成了块设备了,并挂载在 QSPI 上:

在这里插入图片描述
W25Q128 注册成了块设备后,就能将 elmfatfs 这个文件系统挂载到 RT-Thread 的 DFS 上了,如下示例代码:

dfs_mount("W25Q128", "/", "elm", 0, 0)

3.3 文件操作

到此为止,我们就可以使用 libc 的接口进行文件操作了,将接收到的数据以文件方式存放到 W25Q128 里面去,举个简单的例子,如下示例代码:

FILE *recvdata_p0;
recvdata_p0 = fopen("recvdata_p0.csv", "a+");
if (recvdata_p0 != RT_NULL)
{
    fputs((char *)RxBuf_P0, recvdata_p0);
    fputs("\n", recvdata_p0);
    fclose(recvdata_p0);
}

在 Finsh 中输入 “ls” 可以查看当前文件系统中的文件目录,如下图:

在这里插入图片描述
输入 “cat XXX” 可以查看文件内容,如下图:

在这里插入图片描述
简单的几步就可以进行文件操作了,RT-Thread 的文件系统还是相当易用的。

4. 在 SD Card 上使用文件系统

4.1 准备工作

以正点原子的潘多拉开发板 (Iot Board) 为例,教大家在 SD Card 上使用文件系统。

和上面的 SPI Flash 一样,在 menuconfig 中开启相关选项:SD Card,SPI(潘多拉板子的 SD 卡是用 SPI 驱动的而不是 SDIO),libc,DFS,elmfatfs。

4.2 文件系统的挂载

与 SPI Flash 一样,需要将 SD Card 注册成块设备,才能挂载文件系统。如下示例代码:

static int rt_hw_spi1_tfcard(void)
{
    __HAL_RCC_GPIOC_CLK_ENABLE();
    rt_hw_spi_device_attach("spi1", "spi10", GPIOC, GPIO_PIN_3);
    return msd_init("sd0", "spi10");
}
INIT_DEVICE_EXPORT(rt_hw_spi1_tfcard);

在 FinSH 中输入 “list_decive”,即可看到 SD Card 注册成了块设备了,并挂载在 SPI 上:

在这里插入图片描述
SD Card 注册成了块设备后,就能将 elmfatfs 这个文件系统挂载到 RT-Thread 的 DFS 上了,如下示例代码:

dfs_mount("sd0", "/", "elm", 0, 0)

需要注意的是,如果大家手头的板子是使用 SDIO 接口来驱动 SD Card 的,那么将 SD Card 注册成块设备将不用我们操心,RT-Thread 源码中的 “…rt-thread\components\drivers\sdio\block_dev.c” 文件中,会将 SD Card 注册成块设备的。当然,文件系统的挂载还是需要我们手动敲代码去实现的。

4.3 文件操作

与 SPI Flash 一样,可以直接使用 libc 的接口进行文件操作,如下示例代码:

FILE *recvdata_p0;
recvdata_p0 = fopen("recvdata_p0.csv", "a+");
if (recvdata_p0 != RT_NULL)
{
    fputs((char *)RxBuf_P0, recvdata_p0);
    fputs("\n", recvdata_p0);
    fclose(recvdata_p0);
}

5. 针对本次 DIY 的一些优化

5.1 优化1

将文件系统用起来,进行文件操作,是一件相对比较容易的事情。不过当将文件系统运用到实际项目中的时候,往往会因为一些需求或者说是其他因素,导致事情不那么好办。就拿这个 DIY 来说,如果就像上面的示例代码这么用文件系统,虽然系统能正常工作,但是会带来一些问题:

  • 众所周知,文件的操作是需要占用大量时间和资源的,通俗来说就是慢,像文件的读,写,打开,创建等,都是比较慢的。如果发送节点发数据过来,接收节点每收到一条数据,就用文件系统记录这个数据,这样会导致系统性能下降。如何保证减少文件操作次数,提高系统性能,又能保证每条数据都不丢失呢?

这里使用 ringbuffer 来避免这个问题。

ringbuffer 是一种先进先出的 FIFO 环形缓冲区,DIY 的接收节点工程中,我们创建了两个线程去工作,一个是 nrf24l01_thread 线程,用于接收来自发送节点的数据,另一个是 DFS_thread 线程,用于利用文件系统保存数据的。并且创建一个 4KB 大小的一个 ringbuffer:

static struct rt_ringbuffer *recvdatabuf;
recvdatabuf = rt_ringbuffer_create(4069); /* ringbuffer的大小是4KB */

每当 nrf24l01_thread 线程接收到一条数据,就存放到 ringbuffer 中去:

rt_ringbuffer_put(recvdatabuf, (rt_uint8_t *)str_data, strlen(str_data));

DFS_thread 线程中,我们设置一个 ringbuffer 的阈值,这里我将阈值设置成了 ringbuffer 大小的一半,当写入的数据达到了 ringbuffer 的阈值之后,就将 ringbuffer 中所有的数据统统写入文件中去:

/* 判断写入的数据大小到没到所设置的ringbuffer的阈值 */
if (rt_ringbuffer_data_len(recvdatabuf) > (4096 / 2))
{
    /* 到阈值就直接写数据 */
    recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+");
    if (recvdatafile_p0 != RT_NULL)
    {
        while(rt_ringbuffer_data_len(recvdatabuf))
        {
            size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, (4096 / 2));
            fwrite(writebuffer, 1, size, recvdatafile_p0);
        }
        fclose(recvdatafile_p0);
    }
}

这么做,就可以尽可能的减少了文件的操作,提高了系统的性能,同时又保证每一条数据都不会丢失。

5.2 优化2

但是,还有一个问题:

  • 如果发送节点很久很久才发数据过来,或者说是接收节点很久很久才收到数据,那么 ringbuffer 要很久很久才能到阈值。如果这时候,已经写了整整一天的数据进 ringbuffer 中了,只差一点点就要到阈值了,很快就可以将数据写入到文件中去了,这时候偏偏断电了!整整一天的数据白白丢失了,心痛吗?

当然,掉电丢数据这种情况是不可以避免的,但是我们可以通过一些算法优化(姑且叫它算法吧),尽可能的减少丢失数据的可能。

解决思路是:定个固定时间,计时,如果时间一到,此时数据还没写满 ringbuffer 的阈值,这时候就不管数据到没到阈值了,直接将 ringbuffer 里的数据全部写入文件中去。要实现这个思路需要搭配事件集 (event) 使用。

nrf24l01_thread 线程中,每收到一个数据,就发送一个事件:

while (1)
{
    if (!rx_pipe_num_choose())
    {
        /* 通过sscnaf解析收到的数据 */
        if(sscanf((char *)RxBuf_P0, "%d,+%f", &buf.timestamp, &buf.temperature) != 2)
        {
            /* 通过sscnaf解析收到的数据 */
            if(sscanf((char *)RxBuf_P0, "%d,-%f", &buf.timestamp, &buf.temperature) != 2)
            {
                continue;
            }
            buf.temperature = -buf.temperature;
        }
        sprintf(str_data, "%d,%f\n", buf.timestamp, buf.temperature);
        /* 将数据存放到ringbuffer里 */
        rt_ringbuffer_put(recvdatabuf, (rt_uint8_t *)str_data, strlen(str_data));
        /* 收到数据,并将数据存放到ringbuffer里后,才发送事件 */
        rt_event_send(recvdata_event, WRITE_EVENT);
    }
    rt_thread_mdelay(30);
}

DFS_thread 线程中,通过接收两次事件,并设置接收事件的超时时间,达到计时的目的:

while (1)
{
    /* 接收感兴趣的事件WRITE_EVENT,以永久等待方式去接收 */
    if (rt_event_recv(recvdata_event, WRITE_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &set) != RT_EOK)
        continue;
    do
    {
        /* 接收感兴趣的事件WRITE_EVENT,以1000ms超时方式接收 */
        if (rt_event_recv(recvdata_event, WRITE_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(1000), &set) == RT_EOK)
        {
            /* 判断写入的数据大小到没到所设置的ringbuffer的阈值 */
            if (rt_ringbuffer_data_len(recvdatabuf) > THRESHOLD)
            {
                /* 到阈值就直接写数据 */
                recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+");
                if (recvdatafile_p0 != RT_NULL)
                {
                    while(rt_ringbuffer_data_len(recvdatabuf))
                    {
                        size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, THRESHOLD);
                        fwrite(writebuffer, 1, size, recvdatafile_p0);
                    }
                    fclose(recvdatafile_p0);
                }
            }
            /* 阈值没到就继续接收感兴趣的事件WRITE_EVENT,以1000ms超时方式接收 */
            continue;
        }
        /* 1000ms到了,还没有收到感兴趣的事件,这时候不管到没到阈值,直接写 */
        recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+");
        if (recvdatafile_p0 != RT_NULL)
        {
            while(rt_ringbuffer_data_len(recvdatabuf))
            {
                size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, THRESHOLD);
                fwrite(writebuffer, 1, size, recvdatafile_p0);
            }
            fclose(recvdatafile_p0);
        }
    } while(0);
}

这样就尽最大力度的解决了掉电丢失数据的可能了。当然,第二次接收事件的超时时间可以根据自己需求设定长短。

6. 开源代码

为了更进一步便于大家学习,第三周任务的代码已经开源啦~ 请点击这里查看

7. 注意事项

  • 第三周的源码中,只上传了两个 demo 工程,均是本次 DIY 中接收节点的代码

    • RECEIVE(stm32l475-atk-pandora)(SD_Card) 是在 SD Card 上使用文件系统的 demo
    • RECEIVE(stm32l475-atk-pandora)(SPI_Flash) 是在 SPI Flash 上使用文件系统的 demo
  • 发送节点的代码,在第二周的 demo 工程中有,这里不再重复上传相同 demo 工程

  • SPI Flash 的 sector 大小为 4096 字节,需要在 menuconfig 中修改:

在这里插入图片描述

  • SD Card 的 sector 大小为 512 字节,需要在 menuconfig 中修改:

在这里插入图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RT-Thread作品秀】基于RT-Thread智能家居-物联网作者:葫芦侠 概述(说明应用产生的背景、实现功能)智能家居是在互联网影响之下物联化的体现。智能家居通过物联网技术将家中的各种设备连接到一起,提供家电控制、照明控制、电话远程控制、室内外遥控、防盗报警、环境监测、暖通控制、红外转发以及可编程定时控制等多种功能和手段。与普通家居相比,智能家居不仅具有传统的居住功能,兼备建筑、网络通信、信息家电、设备自动化,提供全方位的信息交互功能。 本智能家居应用平台基于STM32F407和ESP8266为主芯片进行设计。主控平台以原子STM32F407开发板为核心,主要任务是通过以太网与设备节点和应用软件进行数据交互。 开发环境(所采用的软、硬件方案)硬件:原子STM32F407探索者、ESP8266、S17021、DS18B20、LED、130电机 RT-Thread版本:rt-thread-3.1.4 开发工具及版本:MDK 5.31,VS CODE,Qt Creator 4.11.1,NetAssist 4.3.26, UartAssist4.3.25 RT-Thread使用情况概述(简要总结下应用中RT-Thread使用情况:内核部分、组件部分、软件包部分、内核、其他)(1)、内核部分:调度器,消息队列。 调度器:创建tcp连接线程。 消息队列:用来实现线程之间的数据传递。 (2)组件部分:网络框架 网络框架:使用tcp/ip建立服务端,用于设备节点和上层控制软件接入。 (3)软件包:Cjson 硬件框架(概述应用所采用的硬件方案框图,并对核心部分做介绍)硬件方案图 智能家居应用平台方案框图如下图所示。主要由三部分组成,分别是主控平台、节点平台和应用软件平台。 系统介绍 主控平台平台与各个节点主控通信,主控通过网络控制和获取各个节点设备数据和状态。上位机应用软件通过网络发送数据到主控,获取设备状态和进行设备控制。 软件框架说明(介绍应用所采用的软件方案框图、流程图等,并加以解说)本项目采用的是STM32主控作为服务器,显示相关节点传感器数据。节点主控、上位机应用软件通过TCP/IP连接至主控。应用软件通过网络将指令发送到主控,主控将指令转发到节点主控,已达到设备控制。节点主控将采集到的数据通过网络发送到STM32主控,STM32显示相关数据,并将数据转发至上位机。 软件模块说明(介绍应用软件关键部分的逻辑、采用的实现方式等)主控平台软件: (1)4.3 TFFLCD (2)界面设计使用SteamWin (3)以太网 演示效果(演示效果请采用3张高清图片,并录制一段不少于1min视频解说应用所实现的效果,视频上传至B站或者腾讯视频或其他视频平台,给出链接即可视频: 比赛感悟(可以围绕这次比赛学到了什么,克服了哪些困难,有哪些收获,不低于200字)本次比赛发现自己的题目做大了,导致时间紧迫。需要完成STM32的代码,ESP8266代码,还有安卓代码。工作时,由于有大量的出差,导致项目一直不能按期推进。由于第一次使用rt-thread,调试多线程,网络、lcd显示花费大量时间。本来打算还要使用触摸屏,但是到最后发现时间已经不够了。由于屏幕使用了图片,下载调试速度慢,也花费了不少的时间。调试ESP8266使用vs code,编译下载特别慢,也花费了不少时间。三个平台之间的通信也是非常的耗费时间。 总之,作为一名技术人员,感觉调试花费的时间要比写代码花费的时间长。
作者:annysky 概述智能家居是目前最火热的应用方向,基于对物联网和智能家居的热衷和喜爱,以自己小屋为实际应用模板,将智能家居的设想变为现实状态。本智能家居应用平台基于STM32H7和STM32F4为主芯片进行设计,分为主控平台和网关平台。主控平台以ART-PI开发板为核心,主要任务是查询网关平台的数据信息、查询和设置参数、与云平台进行交互等;网关平台以STM32F407为核心,主要任务是采集数据和分析处理数据、并将主控平台下发的指令进行分析处理后来控制终端,该平台目前采集4路温湿度、1路电量、1路甲醛、1路PM2.5、2路烟雾报警、1路水浸报警、2路门锁状态的数据,输出控制1路加热和散热,通过CAN通讯与主控平台进行联络,并且使用屏进行显示各个状态,还可以使用WLAN与Onenet进行数据交互。后续根据需要在CAN网络上增加设备或模块(智能窗帘、CAN温湿度传感器、智能继电器、门禁系统、摄像头等),能够监测和控制终端产品。 开发环境硬件: (1)主控:ART-PI(STM32H750XB),ART-PI-DOCK扩展板(含屏) (2)网关:WT-19S42(STM32F407VGT6) (3)RT-Thread版本:RT-Thread V 4.0.3 (4)开发工具及版本:MDK 5.27,CANTest,XCOM V2.0,VGUS2020,DGUS_V759-t5l RT-Thread使用情况概述(1)内核部分:调度器,信号量,消息队列,libcpu/BSP。 调度器:创建多个线程来实现不同的工作。 信号量:用来同步线程。 消息队列:用来实现线程之间传递的数据。 libcpu/BSP:外设CAN驱动、UART驱动。 (2)组件部分:CAN框架,UART框架, CAN框架:使用FDCAN2框架来与网关平台进行数据交互,上层代码可以提高代码的可重用性。 UART框:使用UART框架来与ART-PI-DOCK扩展板进行数据交互。 (3)软件包部分: cJSON:C语言实现的极简的解析 JSON 格式的软件包。 WebNet软件包:由RT-Thread 自主研发的,基于 HTTP 协议的 Web 服务器实现,它不仅提供设备与 HTTP Client 通讯的基本功能,而且支持多种模块功能扩展,且资源占用少、可裁剪性强,充分满足开发者对嵌入式设备服务器的功能需求。 Onenet: RT-Thread 针对 OneNET 平台连接做的的适配,通过这个软件包,可以让设备在 RT-Thread 上非常方便的连接 OneNet 平台,完成数据的发送、接收、设备的注册和控制等功能。 硬件框架总方案原理图 智能家居应用平台方案原理图如图1所示,主要由两部分组成(目前状态,后续根据实际应用情况进行扩展),分别是主控平台和网关平台。 图1总方案原理图 主控平台: 主控平台的硬件分为两部分。一是ART-PI开发板,是由RT-Thread 设计的开源硬件,核心芯片是STM32H750XB,主频最高达480M,支持超低功耗。开发板板载功能众多,标配 TYPE-C 接口,板载 WIFI, 蓝牙配网,还有 LCD(RGB888),SDRAM, TF-Card, USB-OTG等应有尽有。 另外一部分是ART-PI-DOCK扩展板:根据ART-PI开发板的扩展接口自主设计的ART-PI-DOCK扩展板。主要的功能是通讯和人机交互,不参与对外设传感器或者模块的控制和信号采集(除光敏和温湿度)。板子主要包含以下几种功能: (1)2路CAN通讯 (2)1路485通讯; (3)1路232通讯; (4)1路TTL通讯; (5)1路WLAN网络通讯; (6)CAN匹配电阻设置; (7)主机ID设置; (8)1路温湿度传感器; (9)1路光敏传感器; (10)1路锂电池充电管理模块接口; (11)1路外设5V输入接口; (12)1路串口屏接口; (13)1路TFT液晶屏接口。 扩展板涵盖的功能非常全面,包含了目前应该最广泛的几种通讯方式和人机交互方式(可选),可适应不同应用场景。 图2 ART-PI-DOCK原理框图 网关平台 网关核心芯片是STM32F407VGT6,1024k Flash,192KB SRAM,工作频率为168 MHz。基于STM32F407强大的功能,在设计网关平台时,使用了符合IEEE 1588 v2标准要求的以太网MAC10/100,并使用了高达6个USART设备。另外使用了1路CAN,4路串行设备,12路开关量输入,8路开关量输出,各个接口均进行了标识。 图3网关平台原理框图 软件框架说明图4 主控平台软件流程图 图5 网关平台软件流程图 本项目采用的是主控与网关两个平台,主控平台完成对网关平台数据的处理显示,并对网关平台的参数

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值