Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
2024-RSOC
AHT10
【2024-RSOC】利用RT-Thread快速开发温湿度物联网监测器【Day5】
发布于 2024-07-27 13:06:25 浏览:118
订阅该版
[tocm] # 【2024-RSOC】利用RT-Thread快速开发温湿度物联网监测器【Day5】 ## 监测器基本功能 ### 要求: 1.使用AHT10软件包采集温湿度并上传到阿里云; 2.梳理文件系统启动流程,熟悉shell命令使用。能够将font分区给挂载上。在温湿度上传上传云端的同时,将数据同步放在文件系统处,文件名为:Data.txt; 文件内容: ``` Temp:XX ; Humi:XX ; Count: 1(自上电起所采集的数据次数) Temp:XX ; Humi:XX ; Count: 2(自上电起所采集的数据次数) Temp:XX ; Humi:XX ; Count: 3(自上电起所采集的数据次数) ``` 3.利用云端给开发板发送指令然后实现小灯翻转; ### 硬件及开发环境 - 硬件:星火一号开发板 - env环境:scons工具、menuconfig工具 - 编辑器:vscode - RT-Thread源代码:`rt-thread\bsp\stm32\stm32f407-rt-spark\dist\project\`工程下,通过`scons --dist`命令在`rt-thread\bsp\stm32\stm32f407-rt-spark`目录下打包的完整独立原始工程。 ## 一、软件包与组件 ### 软件包(Software Package) #### 定义 具有特定的功能,用来完成特定任务的一个程序或一组程序。可以粗略地把他理解为帮助我们完成了底层驱动的编写,我们只需要使用里面提供的API就好了。 #### 功能 简化开发过程。它们通常封装了复杂的底层细节,使开发者能够专注于应用逻辑,而不必花费大量时间在底层驱动和硬件接口上。例如,在我们的温湿度物联网监测器中,我们将使用AHT10软件包来采集温湿度数据。这使得我们无需编写复杂的I2C通信代码,只需调用AHT10包中的API即可获取传感器数据。 RT-Thread 软件包中心:[软件包 - RT-Thread物联网操作系统](https://packages.rt-thread.org/index.html) ![day5软件包.png](https://oss-club.rt-thread.org/uploads/20240727/888eb8a68879fc7060c77068275e97ee.png.webp) ### 组件(Component) #### 定义 组件是一个可以独立开发、测试、部署和维护的软件单元。它们通常具有明确的功能边界,可以在不同的项目中重复使用。组件不仅可以封装应用层逻辑,还可以封装底层驱动、协议栈等。 #### 功能 组件的主要功能是提高软件的模块化和可维护性。通过将不同功能封装成独立的组件,开发者可以更方便地进行功能扩展、维护和测试。例如,在我们的项目中,文件系统、网络协议栈(如MQTT)等都可以看作是独立的组件。 ## 二、温湿度传感器——AHT21(I2C设备) ### 打开板载的AHT外设驱动 1. 打开板上外设AHT32开关 2. 因为Kconfig里面关联了,所以不用再次打开软件包开启IIC总线 3. 查看确认一下 4. 搜索rt_vsprintf_full软件包(支持浮点输出) 5. 使用`pkgs –update`,下载软件包 ### 任务代码编写 以下代码演示了如何编写任务代码来读取AHT21传感器的温湿度数据,并将数据存储在消息队列中。 ```c #include
#include
#include
#include
#include "aht10.h" #define LOG_TAG "app_aht21" #define LOG_LVL LOG_LVL_DBG #include
static float humidity, temperature; static aht10_device_t dev; static int count; /* 总线名称 */ static const char *i2c_bus_name = "i2c3"; //新建一个线程专门用于读取传感器温湿度数据并将数据分别放入两个消息队列中 static rt_thread_t thread1 = RT_NULL; rt_mq_t mq_hum = RT_NULL; rt_mq_t mq_tem = RT_NULL; /* 定义一个线程入口 */ static void thread1_entry(void *parameter) { while (1) { /* 读取湿度 */ humidity = aht10_read_humidity(dev); // LOG_D("humidity : %d.%d %%", (int)humidity, (int)(humidity * 10) % 10); rt_mq_send(mq_hum, &humidity, sizeof(humidity)); /* 读取温度 */ temperature = aht10_read_temperature(dev); // LOG_D("temperature: %d.%d", (int)temperature, (int)(temperature * 10) % 10); rt_mq_send(mq_tem, &temperature, sizeof(temperature)); rt_thread_mdelay(500); } } static int app2_aht21_example(void) { /* 初始化 aht10 */ dev = aht10_init(i2c_bus_name); if (dev == RT_NULL) { LOG_E(" The sensor initializes failure"); return 0; } // 初始化消息队列 mq_hum = rt_mq_create("mq_hum", 10, sizeof(humidity), RT_IPC_FLAG_FIFO); mq_tem = rt_mq_create("mq_tem", 10, sizeof(temperature), RT_IPC_FLAG_FIFO); // 创建线程 thread1 = rt_thread_create("thread1", thread1_entry, RT_NULL, 1024, 25, 10); if (thread1 != RT_NULL) { rt_thread_startup(thread1); } return RT_EOK; } /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(app2_aht21_example, app_aht21_example); ``` 上面地代码中定义了一些宏和全局变量,比如日志的标签和级别、用于存储湿度和温度的变量、传感器设备的句柄、I2C总线的名称、线程以及消息队列。 接下来,编写了一个线程入口函数`thread1_entry`。这个函数会一直循环,定期从AHT10传感器读取湿度和温度数据。读取到的湿度数据通过一个消息队列发送出去,温度数据也通过另一个消息队列发送出去。这个过程每隔500毫秒进行一次,保证数据是实时和连续的。 为了方便调试和测试,还使用了一个宏把这个初始化函数导出为RT-Thread的shell命令,这样我们就可以在RT-Thread的shell里通过命令行来执行这个函数,启动我们的传感器数据读取和传递功能。 ## 三、RW007 WIFI 连接(SPI设备) ### 打开板载的RW007外设驱动 ![day5MQTT配置1.png](https://oss-club.rt-thread.org/uploads/20240727/8e0e45afb4b990900ca27534c62de396.png) 我们还需要进入这个选项进一步修改硬件引脚配置。 ``` (90) RW007 CS pin index (29) RW007 BOOT0 pin index (same as spi clk pin) (90) RW007 BOOT1 pin index (same as spi cs pin) (107) RW007 INT/BUSY pin index (111) RW007 RESET pin index ``` 配置完后就在env环境中使用如下命令更新软件包 ``` pkgs --update [Use Gitee server - manually decision] 100%|███████████████████████████████████████████████████| 16/16 [00:01<00:00, 11.59it/s] ==============================> RW007 v2.1.0 is downloaded successfully. Operation completed successfully. ``` 烧录上电后就可以使用如下命令对wifi进行操作了。 ```shell msh />wifi wifi wifi help wifi scan [SSID] wifi join [SSID] [PASSWORD] wifi ap SSID [PASSWORD] wifi disc wifi ap_stop wifi status wifi smartconfig ``` 可以使用如下命令来连接wifi ```c msh />wifi join 43324 433433433 [I/WLAN.mgnt] wifi connect success ssid:43324 msh />[I/WLAN.lwip] Got IP address : 192.168.1.102 ``` ### 任务代码编写 ```c // #include
#include
#define WIFI_SSID "43324" #define WIFI_PASSWORD "433433433" rt_err_t rt_wlan_connect(const char *ssid, const char *password); static void app1_wifi_connect(void) { rt_err_t result; result = rt_wlan_connect(WIFI_SSID, WIFI_PASSWORD); if (result != RT_EOK) { rt_kprintf("wifi connect failed\n"); } else { rt_kprintf("wifi connect success\n"); } } MSH_CMD_EXPORT(app1_wifi_connect, wifi connect); ``` 由于这里我们只需要简单的连接wifi就好,所以写地很简单声明了连接函数,直接调用。 ## 四、阿里云mqtt连接 ### MQTT协议(搭配阿里云平台) #### 原理 MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布-订阅模式的消息传输协议,适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通信。 特点: - **轻量级:**物联网设备通常在处理能力、内存和能耗方面受到限制。MQTT 开销低、报文小的特点使其非常适合这些设备,因为它消耗更少的资源,即使在有限的能力下也能实现高效的通信。 - **可靠:**物联网网络常常面临高延迟或连接不稳定的情况。MQTT 支持多种 QoS 等级、会话感知和持久连接,即使在困难的条件下也能保证消息的可靠传递,使其非常适合物联网应用。 三种Qos等级:最多一次(QoS0)、至少一次(QoS1)、仅一次(QoS2) - **便于拓展:**如果有设备需要获取某个传感器的消息,只需要订阅这个主题就好了。 #### 运行框架 **Client:**客户端,即我们使用的设备。 使用MQTT的程序或设备。客户端总是通过网络连接到服务端。它可以 - 发布应用消息给其它相关的客户端。 - 订阅以请求接受相关的应用消息。 - 取消订阅以移除接受应用消息的请求。 - 从服务端断开连接。 **Server:**服务端 作为发送消息的客户端和请求订阅的客户端之间的中介。服务端 - 接受来自客户端的网络连接。 - 接受客户端发布的应用消息。 - 处理客户端的订阅和取消订阅请求。 - 转发应用消息给符合条件的已订阅客户端。 **Topic Name:**主题名 附加在应用消息上的一个标签,服务端已知且与订阅匹配。服务端发送应用消息的一个副本给每一个匹配的客户端订阅。 **Subscription:** 订阅 订阅相应的主题名来获取对应的信息。 **Publish:**发布 在对应主题上发布新的消息。 ![day5mqtt框架.png](https://oss-club.rt-thread.org/uploads/20240727/877267eb6aa15243d6605adacef4d671.png.webp) ### Ali-iotkit使能 在软件包中找到阿里云的软件包,使能后需要修改里面的一些参数,主要是如下列出的四个,需要与阿里云中我们创建的设备和产品参数一致。 ```c RT-Thread online packages ---> ->IoT - internet of things ---> IoT Cloud ---> [*] Ali-iotkit: Aliyun cloud sdk 'iotkit-embedded' for RT-Thread ---> (a1dSQSGZ77X) Config Product Key (NEW) (NfIdVcfBP7rtH24H) Config Product Secret (NEW) (RGB-LED-DEV-1) Config Device Name (NEW) (Ghuiyd9nmGowdZzjPqFtxhm3WUHEbIlI) Config Device Secret (NEW) [*]sample ``` 在阿里云中找到这些参数并修改进去 ![day5阿里云产品参数.png](https://oss-club.rt-thread.org/uploads/20240727/c8b3330dd0d8b08c766e6a5d69496401.png.webp) ![day5阿里云产设备参数.png](https://oss-club.rt-thread.org/uploads/20240727/19ff7450657e181bf96dced3aa08491b.png.webp) 由于勾选了阿里云的例程,在命令行中连接好WiFi,再输入以下例程命令,不出意外的话就可以正常连接阿里云服务器了。 ``` msh />mqtt_example msh />mqtt_example_main|135 :: mqtt example host name:hrnzsz0jp7o.iot-as-mqtt.cn-shanghai.aliyuncs.com > { > "id": "0", > "version": "1.0", > "params": [ > { > "attrKey": "SYS_LP_SDK_VERSION", > "attrValue": "3.0.1", > "domain": "SYSTEM" > }, > { > "attrKey": "SYS_SDK_LANGUAGE", > "attrValue": "C", > "domain": "SYSTEM" > } > ], > "method": "thing.deviceinfo.update" > } > { > "id": "1", > "params": { > "version": "app-1.0.0-20180101.1000" > } > } > { > "message": "hello!" > } example_event_handle|108 :: msg->event_type : 9 example_event_handle|108 :: msg->event_type : 9 example_event_handle|108 :: msg->event_type : 3 < { < "message": "hello!" < } ``` 后面感觉由于发送和接收的消息一直在shell终端回显,于是追踪了回显打印的函数,发现是由`FEATURE_INFRA_NETWORK_PAYLOAD`这个宏控制的,在env环境下搜索即可找到并取消选中, ``` (Top) → RT-Thread online packages → IoT - internet of things → IoT Cloud → Ali-iotkit: Aliyun cloud sdk 'iotkit-embedded' for RT-Thread [ ] FEATURE_INFRA_NETWORK_PAYLOAD ``` ### 任务代码编写 ```c #include
#include
#include
#ifndef RT_USING_NANO #include
#endif /* RT_USING_NANO */ #include "rtthread.h" #include "dev_sign_api.h" #include "mqtt_api.h" #define GPIO_LED_B GET_PIN(F, 11) #define LOG_TAG "app_mqtt" #define LOG_LVL LOG_LVL_DBG #include
extern rt_mq_t mq_hum; extern rt_mq_t mq_tem; static char DEMO_PRODUCT_KEY[IOTX_PRODUCT_KEY_LEN + 1] = {0}; static char DEMO_DEVICE_NAME[IOTX_DEVICE_NAME_LEN + 1] = {0}; static char DEMO_DEVICE_SECRET[IOTX_DEVICE_SECRET_LEN + 1] = {0}; void *HAL_Malloc(uint32_t size); void HAL_Free(void *ptr); void HAL_Printf(const char *fmt, ...); int HAL_GetProductKey(char product_key[IOTX_PRODUCT_KEY_LEN + 1]); int HAL_GetDeviceName(char device_name[IOTX_DEVICE_NAME_LEN + 1]); int HAL_GetDeviceSecret(char device_secret[IOTX_DEVICE_SECRET_LEN]); uint64_t HAL_UptimeMs(void); int HAL_Snprintf(char *str, const int len, const char *fmt, ...); #define EXAMPLE_TRACE(fmt, ...) \ do { \ HAL_Printf("%s|%03d :: ", __func__, __LINE__); \ HAL_Printf(fmt, ##__VA_ARGS__); \ HAL_Printf("%s", "\r\n"); \ } while(0) static void example_message_arrive(void *pcontext, void *pclient, iotx_mqtt_event_msg_pt msg) { iotx_mqtt_topic_info_t *topic_info = (iotx_mqtt_topic_info_pt) msg->msg; switch (msg->event_type) { case IOTX_MQTT_EVENT_PUBLISH_RECEIVED: /* print topic name and topic message */ EXAMPLE_TRACE("Message Arrived:"); EXAMPLE_TRACE("Topic : %.*s", topic_info->topic_len, topic_info->ptopic); EXAMPLE_TRACE("Payload: %.*s", topic_info->payload_len, topic_info->payload); EXAMPLE_TRACE("\n"); //查找payload中的数据是否等于字符串"led1",如果是则点亮LED // "led0",如果是则熄灭LED // "led2",如果是则切换LED状态 if (strstr(topic_info->payload, "led1") != NULL) { rt_pin_write(GPIO_LED_B, PIN_HIGH); rt_kprintf("led on\n"); } else if (strstr(topic_info->payload, "led0") != NULL) { rt_pin_write(GPIO_LED_B, PIN_LOW); rt_kprintf("led off\n"); } else if (strstr(topic_info->payload, "led2") != NULL) { rt_pin_write(GPIO_LED_B, !rt_pin_read(GPIO_LED_B)); rt_kprintf("led toggle\n"); } break; default: break; } } static int example_subscribe(void *handle) { int res = 0; const char *fmt = "/%s/%s/user/get"; char *topic = NULL; int topic_len = 0; topic_len = strlen(fmt) + strlen(DEMO_PRODUCT_KEY) + strlen(DEMO_DEVICE_NAME) + 1; topic = HAL_Malloc(topic_len); if (topic == NULL) { EXAMPLE_TRACE("memory not enough"); return -1; } memset(topic, 0, topic_len); HAL_Snprintf(topic, topic_len, fmt, DEMO_PRODUCT_KEY, DEMO_DEVICE_NAME); res = IOT_MQTT_Subscribe(handle, topic, IOTX_MQTT_QOS0, example_message_arrive, NULL); if (res < 0) { EXAMPLE_TRACE("subscribe failed"); HAL_Free(topic); return -1; } HAL_Free(topic); return 0; } // 该函数用来从消息队列中读取温湿度数据组合成payload数据格式并传入example_publish函数 static void mqtt_tranfer_sensor(void *pclient) { float humidity; float temperature; char *payload = NULL; int payload_len = 0,res = 0; const char *fmt = "/sys/%s/%s/thing/event/property/post"; char *topic = NULL; int topic_len = 0; topic_len = strlen(fmt) + strlen(DEMO_PRODUCT_KEY) + strlen(DEMO_DEVICE_NAME) + 1; topic = HAL_Malloc(topic_len); if (topic == NULL) { EXAMPLE_TRACE("memory not enough"); return; } memset(topic, 0, topic_len); HAL_Snprintf(topic, topic_len, fmt, DEMO_PRODUCT_KEY, DEMO_DEVICE_NAME); rt_mq_recv(mq_hum, &humidity, sizeof(humidity), RT_WAITING_NO); // LOG_D("humidity : %d.%d %%", (int)humidity, (int)(humidity * 10) % 10); /* 从队列中读取温度并打印 */ rt_mq_recv(mq_tem, &temperature, sizeof(temperature), RT_WAITING_NO); // LOG_D("temperature: %d.%d", (int)temperature, (int)(temperature * 10) % 10); // 判断数据是否合法 if (humidity < 0 || humidity > 100 || temperature < -40 || temperature > 85) { EXAMPLE_TRACE("data error"); return; } //计算payload所需长度,温度湿度数据保留一位小数上传,"params":{"CurrentTemperature":16.5,"CurrentHumidity":56.3} payload_len = strlen("{\"params\":{\"CurrentTemperature\":16.5,\"CurrentHumidity\":56.3}}") + 3; payload = HAL_Malloc(payload_len); if (payload == NULL) { EXAMPLE_TRACE("memory not enough"); return; } memset(payload, 0, payload_len); HAL_Snprintf(payload, payload_len, "{\"params\":{\"CurrentTemperature\":%.1f,\"CurrentHumidity\":%.1f}}", temperature, humidity); res = IOT_MQTT_Publish_Simple(0, topic, IOTX_MQTT_QOS0, payload, strlen(payload)); if (res < 0) { EXAMPLE_TRACE("publish failed, res = %d", res); HAL_Free(topic); HAL_Free(payload); return; } HAL_Free(topic); HAL_Free(payload); } static void example_event_handle(void *pcontext, void *pclient, iotx_mqtt_event_msg_pt msg) { EXAMPLE_TRACE("msg->event_type : %d", msg->event_type); } static void mqtt_example_main(void *parameter) { void *pclient = NULL; int res = 0; int loop_cnt = 0; iotx_mqtt_param_t mqtt_params; HAL_GetProductKey(DEMO_PRODUCT_KEY); HAL_GetDeviceName(DEMO_DEVICE_NAME); HAL_GetDeviceSecret(DEMO_DEVICE_SECRET); EXAMPLE_TRACE("mqtt example"); /* Initialize MQTT parameter */ memset(&mqtt_params, 0x0, sizeof(mqtt_params)); mqtt_params.handle_event.h_fp = example_event_handle; pclient = IOT_MQTT_Construct(&mqtt_params); if (NULL == pclient) { EXAMPLE_TRACE("MQTT construct failed"); return; } res = example_subscribe(pclient); if (res < 0) { IOT_MQTT_Destroy(&pclient); return; } while (1) { if (0 == loop_cnt % 20) { // example_publish(pclient); mqtt_tranfer_sensor(pclient); } IOT_MQTT_Yield(pclient, 200); loop_cnt += 1; } return; } // #ifdef FINSH_USING_MSH // MSH_CMD_EXPORT_ALIAS(mqtt_example_main, ali_mqtt_sample, ali coap sample); // #endif static char mqtt_thread_stack[4096]; static struct rt_thread mqtt_thread; static int app3_mqtt_example() { rt_thread_init(&mqtt_thread, "mqtt_thread", mqtt_example_main, RT_NULL, mqtt_thread_stack, sizeof(mqtt_thread_stack), 20, 10); rt_thread_startup(&mqtt_thread); return 0; } MSH_CMD_EXPORT(app3_mqtt_example, app_ali_mqtt_sample); ``` 这段代码的整体流程是:初始化传感器和MQTT客户端 -> 从传感器读取数据 -> 通过MQTT发送数据 -> 接收MQTT命令控制LED。所有这些功能都封装在不同的函数中,分别负责初始化、数据读取、数据发送和命令处理,最后通过一个主线程把它们串联起来。 我们实现了一个函数`example_message_arrive`,这个函数在收到MQTT消息时调用。它会检查消息的内容,如果消息包含"led1",就点亮LED;如果包含"led0",就熄灭LED;如果包含"led2",就切换LED的状态。 接下来是`example_subscribe`函数,它用来订阅MQTT主题,主题格式是`/产品密钥/设备名称/user/get`。订阅成功后,MQTT服务器发送到这个主题的消息会调用`example_message_arrive`处理。 为了读取传感器数据并通过MQTT发送,我们定义了`mqtt_tranfer_sensor`函数。这个函数从消息队列中读取温湿度数据,检查数据是否合法,然后构建一个JSON格式的payload,最后调用`IOT_MQTT_Publish_Simple`函数把数据发布到MQTT服务器。 `mqtt_example_main`是主函数,负责初始化MQTT客户端,订阅主题,并在一个循环中定期调用`mqtt_tranfer_sensor`函数发送传感器数据。这个循环每隔一段时间发送一次数据。 ## 五、SPI FLASH文件系统 ### 文件系统定义 **DFS** 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System。 #### 文件系统架构 在 RT-Thread DFS 中,文件系统有统一的根目录,使用 `/` 来表示。 ![day5fs-dir.png](https://oss-club.rt-thread.org/uploads/20240727/d6ab473315771ee2404083c0c0c3c481.png) ![day5fs-layer.png](https://oss-club.rt-thread.org/uploads/20240727/f1c99113975066afadaa814a2fcaa7e5.png) #### 文件系统种类 | 类型 | 特点 | | ----- | :----------------------------------------------------------: | | FatFS | FatFS 是专为小型嵌入式设备开发的一个兼容微软 FAT 格式的文件系统,采用ANSI C编写,具有良好的硬件无关性以及可移植性,是 RT-Thread 中最常用的文件系统类型。我们今天使用到的elm_fat就是这个类型。 | | RomFS | 传统型的 RomFS 文件系统是一种简单的、紧凑的、只读的文件系统,不支持动态擦写保存,按顺序存放数据,因而支持应用程序以 XIP(execute In Place,片内运行) 方式运行,在系统运行时, 节省 RAM 空间。我们一般拿其作为挂载根目录的文件系统 | | DevFS | 即设备文件系统,在 RT-Thread 操作系统中开启该功能后,可以将系统中的设备在 `/dev` 文件夹下虚拟成文件,使得设备可以按照文件的操作方式使用 read、write 等接口进行操作。 | | UFFS | UFFS 是 Ultra-low-cost Flash File System(超低功耗的闪存文件系统)的简称。它是国人开发的、专为嵌入式设备等小内存环境中使用 Nand Flash 的开源文件系统。与嵌入式中常使用的 Yaffs 文件系统相比具有资源占用少、启动速度快、免费等优势。 | | NFS | NFS 网络文件系统(Network File System)是一项在不同机器、不同操作系统之间通过网络共享文件的技术。在操作系统的开发调试阶段,可以利用该技术在主机上建立基于 NFS 的根文件系统,挂载到嵌入式设备上,可以很方便地修改根文件系统的内容。 | #### POSIX接口层 POSIX 表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写 POSIX),POSIX 标准定义了操作系统应该为应用程序提供的接口标准,是 IEEE 为要在各种 UNIX 操作系统上运行的软件而定义的一系列 API 标准的总称。 > 文件描述符:`file descriptor`(fd),每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也可能指向同一个文件。可以简单理解为它可以帮助我们找到我们需要的文件。 在文件系统中它提供了四个重要的接口: ![day5fs-mg.png](https://oss-club.rt-thread.org/uploads/20240727/a6b5d417004ae774f878d736c1f1211d.png) 还有一些其他常用的API: | API | 描述 | | ----------------------------------------------- | ------------ | | int `rename`(const char *old, const char *new); | 文件重命名 | | int `stat`(const char *file, struct stat *buf); | 获取文件状态 | | int `unlink`(const char *pathname); | 删除文件 | #### 目录管理 除了文件的管理之外我们还需要对目录进行管理,管理使用的API: ![day5fs-dir-mg.png](https://oss-club.rt-thread.org/uploads/20240727/87c60b694f0e413c23c4b6a7cec3b15c.png) ### 文件系统启动流程 #### DFS组件初始化 在这个环节中我们会初始化文件系统注册表、有关文件描述符的各种类型的锁。如果使用了设备文件系统的化还会对其做一定的初始化。 #### 各文件系统注册到DFS中 各自文件系统调用自己的一个init函数注册到DFS中。就是将上面所将的FatFS,RomFS...注册到DFS中等待使用。 #### 挂载根目录 使用RomFS创建一个简单的`”/“`根目录,使用到的其他文件系统需要挂载到这个根目录下做拓展。 ### 文件系统结合FAL配置W25Q64 首先会在`rt_hw_spi_flash()`(在INIT_COMPONENT_EXPORT)中会把一个`spi20`的spi设备挂载在`spi2`总线上,然后通过`rt_sfud_flash_probe`将这个`spi20`设备跟一个`SPI_FLASH`设备(命名为`W25Q64`)进行绑定。然后在FAL中对这个`SPI_FLASH(W25Q64)`设备进行分区,然后对相应的区创建`BLK设备`。 接着我们对这个`BLK设备`进行格式化(即挑选一种文件系统去管理这个BLK设备),然后将这个格式化好的文件系统进行挂载(分配到对应路径上)这样我们就可以使用组件的`API`对`W25Q64`进行读写了。这里使用了POSIX协议接口,我们只需要使用`open()`、`close()`、`read()`、`write()`就可以完成对文件的读写。也可以用`mkdir()`、`opendir()`、`readdir()`、`closedir()`来对目录进行管理。 1. 开启板上外设 ![day5文件系统开关1.png](https://oss-club.rt-thread.org/uploads/20240727/f512f1cf85ef8b55ab9225371eca6c71.png) 2. 配置自动挂载 ![day5文件系统开关2.png](https://oss-club.rt-thread.org/uploads/20240727/6a0d8f4ee14f568fa8c032e761147221.png) 3. 配置Component组件 ![day5文件系统开关3.png](https://oss-club.rt-thread.org/uploads/20240727/bcfcd2bb0ab970c2db0a397ce2a34e27.png) 4. 配置DFS ![day5文件系统开关4.png](https://oss-club.rt-thread.org/uploads/20240727/d61c80b64cf507fcb380e3429ad9c832.png) 5. 配置elmFat ![day5文件系统开关5.png](https://oss-club.rt-thread.org/uploads/20240727/625d90c6d47c025c320aa53b210bc801.png) ### 新建块设备并挂载 在`rt-thread\bsp\stm32\stm32f407-rt-spark\dist\project\board\ports\drv_filesystem.c` line59新加下面这行代码 ``` /* 在 spi flash 中名为 "font" 的分区上创建一个块设备 */ struct rt_device *flash_dev = fal_blk_device_create("font"); ``` 此时再编译并烧录即可观察到系统启动初始化时自动打印出font块设备创建成功的提示信息。 ``` \ | / - RT - Thread Operating System / | \ 5.2.0 build Jul 26 2024 22:40:06 2006 - 2024 Copyright by RT-Thread team lwIP-2.0.3 initialized! [I/SFUD] Found a Winbond flash chip. Size is 8388608 bytes. [I/SFUD] W25Q64 flash device initialized successfully. [I/SFUD] Probe SPI flash W25Q64 by SPI device spi20 success. [I/sal.skt] Socket Abstraction Layer initialize success. [D/FAL] (fal_flash_init:47) Flash device | onchip_flash_128k | addr: 0x08000000 | len: 0x00100000 | blk_size: 0x00020000 |initialized finish. [D/FAL] (fal_flash_init:47) Flash device | W25Q64 | addr: 0x00000000 | len: 0x00800000 | blk_size: 0x00001000 |initialized finish. [I/FAL] ==================== FAL partition table ==================== [I/FAL] | name | flash_dev | offset | length | [I/FAL] ------------------------------------------------------------- [I/FAL] | app | onchip_flash_128k | 0x00000000 | 0x00060000 | [I/FAL] | param | onchip_flash_128k | 0x00060000 | 0x000a0000 | [I/FAL] | easyflash | W25Q64 | 0x00000000 | 0x00080000 | [I/FAL] | download | W25Q64 | 0x00080000 | 0x00100000 | [I/FAL] | wifi_image | W25Q64 | 0x00180000 | 0x00080000 | [I/FAL] | font | W25Q64 | 0x00200000 | 0x00300000 | [I/FAL] | filesystem | W25Q64 | 0x00500000 | 0x00300000 | [I/FAL] ============================================================= [I/FAL] RT-Thread Flash Abstraction Layer initialize success. [I/FAL] The FAL block device (filesystem) created successfully [I/app.filesystem] Filesystem initialized! msh />[E/[RW007]] The wifi Stage 1 status 0 0 0 1 [I/WLAN.dev] wlan init success [I/WLAN.lwip] eth device init ok name:w0 [I/WLAN.dev] wlan init success [I/WLAN.lwip] eth device init ok name:w1 rw007 sn: [rw007c745bb22fc584aa6df36] rw007 ver: [RW007_2.1.0-a7a0d089-57] ``` 此时在串口终端输入list device命令即可看到列出的设备中包括刚刚初始化的font块设备。 ``` msh />list device device type ref count -------- -------------------- ---------- w1 Network Interface 1 w0 Network Interface 1 wlan0 Network Interface 1 wlan1 Network Interface 1 wspi SPI Device 0 font Block Device 0 filesyst Block Device 1 W25Q64 Block Device 0 spi20 SPI Device 0 spi2 SPI Bus 0 i2c3 I2C Bus 0 uart1 Character Device 2 pin Pin Device 0 ``` 挂载过程如下: ``` msh /fal>mkdir fontdir [603100] W/time: Cannot find a RTC device! msh /fal>ls Directory /fal: fontdir
msh /fal>mkfs -t elm font [625190] W/time: Cannot find a RTC device! msh /fal>mount font fontdir elm mount device font(elm) onto fontdir ... succeed! ``` ### 任务代码编写 ```c //记得在menuconfig中开启支持旧版本功能(Support legacy version) #include
#include
#include
#include
//需要添加软件包进这里 // 在温湿度上传上传云端的同时,将数据同步放在文件系统处,文件名为:Data.txt; // 文件内容: Temp:XX ; Humi:XX ; Count: 1(自上电起所采集的数据次数) extern rt_mq_t mq_hum; extern rt_mq_t mq_tem; // 定义文件名 char file_name[] = "/fal/Data.txt"; // 定义文件内容 static char file_content[100] = {}; // 定义一个函数用来从队列中获取数据并写入文件 void write_data_to_file(void *parameter) { // 定义文件描述符 int fd; // 定义文件内容的长度 int len = 0; // 定义温度和湿度 float temperature, humidity; // 定义次数 static int count = 1; memset(file_content, 0, sizeof(file_content)); // 从队列中获取温度和湿度 rt_mq_recv(mq_hum, &humidity, sizeof(humidity), RT_WAITING_NO); rt_mq_recv(mq_tem, &temperature, sizeof(temperature), RT_WAITING_NO); len = rt_snprintf(file_content, sizeof(file_content), "Temp: %.2f ; Humi: %.2f ; Count: %d\n", temperature, humidity, count); // 创建文件 fd = open(file_name, O_WRONLY | O_CREAT); if (fd >= 0) { // rt_kprintf("Openfile done.\n"); // 将数据写入文件末尾 lseek(fd, 0, SEEK_END); write(fd, file_content, len); // 关闭文件 close(fd); count++; // rt_kprintf("Write done.\n"); } else { rt_kprintf("File Open Fail.\n"); } } // 定义一个线程用来写入文件 static rt_thread_t thread2 = RT_NULL; static char thread2_stack[2048]; void write_data_to_file_entry(void *parameter) { while (1) { write_data_to_file(RT_NULL); rt_thread_mdelay(5000); } } // 定义一个函数用来创建线程 void app4_write_data(void) { thread2 = rt_thread_create("thread2", write_data_to_file_entry, RT_NULL, sizeof(thread2_stack), 25, 10); if (thread2 != RT_NULL) { rt_thread_startup(thread2); } } // 导出命令 MSH_CMD_EXPORT(app4_write_data, write_data_thread); ``` 为了从消息队列中获取温湿度数据并将其写入文件,我们定义了一个函数`write_data_to_file`。这个函数首先定义了一些变量,包括文件描述符、温度、湿度和计数器。然后,它从消息队列中读取温湿度数据,并将数据格式化为字符串,保存在文件内容缓冲区中。接下来,函数打开或创建一个文件,并将格式化的字符串写入文件末尾。写入完成后,文件被关闭,计数器增加。 为了定期执行这个数据写入操作,我们创建了一个线程。线程的入口函数`write_data_to_file_entry`在一个无限循环中,每隔5秒调用一次`write_data_to_file`函数。 注意有时在启动时可能会出现filesystem初始化挂载失败的现象,感觉这种情况还挺常见的,可能时由于flash和wifi挂载在了同一个spi上,可以尝试在工程中添加如下所示的一段代码,用来在系统启动时拉低wifi的spi片选。 ```c // cs:90 (f-a)*16+10 #define WIFI_CS GET_PIN(F,10) void WIFI_CS_PULL_DOWN(void) { rt_pin_mode(WIFI_CS, PIN_MODE_OUTPUT); rt_pin_write(WIFI_CS, PIN_LOW); } INIT_BOARD_EXPORT(WIFI_CS_PULL_DOWN); ``` 同时若是仅只有文件系统最后一步失败可以尝试使用如下命令手动初始化文件系统。 `mkfs -t elm filesystem` ## 六、实验现象解释 ### 1. 连接Wi-Fi ```shell msh />app1_wifi_connect [13646] I/WLAN.mgnt: wifi connect success ssid:43324 wifi connect success msh />[14654] I/WLAN.lwip: Got IP address : 192.168.1.102 ``` 当运行 `app1_wifi_connect` 命令时,设备成功连接到SSID为“43324”的Wi-Fi网络,并且获取到IP地址 `192.168.1.102`。这表明设备已成功加入本地网络,并可以进行网络通信。 ### 2. 启动温湿度传感器 ```shell msh />app2_aht21_example ``` 运行 `app2_aht21_example` 命令启动AHT21温湿度传感器的示例程序,准备读取温湿度数据。 ### 3. 启动MQTT客户端并订阅主题 ```shell msh />app3_mqtt_example msh />mqtt_example_main|207 :: mqtt example host name:hrnzsz0jp7o.iot-as-mqtt.cn-shanghai.aliyuncs.com example_event_handle|192 :: msg->event_type : 9 example_event_handle|192 :: msg->event_type : 3 example_event_handle|192 :: msg->event_type : 9 ``` 运行 `app3_mqtt_example` 命令启动MQTT客户端,连接到阿里云的MQTT服务器 `hrnzsz0jp7o.iot-as-mqtt.cn-shanghai.aliyuncs.com`。消息事件类型9和3表明MQTT连接已成功建立并开始处理消息。 ### 4. 接收到MQTT消息并控制LED ```shell msh />example_message_arrive|049 :: Message Arrived: example_message_arrive|050 :: Topic : /hrnzsz0Jp7o/dev_spark1/user/get example_message_arrive|051 :: Payload: led1 example_message_arrive|052 :: led on ``` 当设备接收到主题 `/hrnzsz0Jp7o/dev_spark1/user/get` 上的消息时,消息的负载为 `led1`,设备通过控制GPIO点亮LED,并输出 `led on` 提示。 ### 5. 启动数据写入文件系统 ```shell msh />app4_write_data msh />[110594] W/time: Cannot find a RTC device! msh />[115757] W/time: Cannot find a RTC device! ``` 运行 `app4_write_data` 命令启动一个线程,将温湿度数据定期写入文件 `Data.txt`。启动时提示找不到RTC设备,但不影响数据写入。 ### 6. 检查文件内容 ```shell ls Directory /: fal
msh />cd fal[120925] W/time: Cannot find a RTC device! msh /fal>[126090] W/time: Cannot find a RTC device! ls Directory /fal: Data.txt 823 msh /fal>[131257] W/time: Cannot find a RTC device! msh /fal>cat Data.txt Temp: 28.70 ; Humi: 77.91 ; Count: 1 Temp: 28.34 ; Humi: 79.04 ; Count: 2 Temp: 28.39 ; Humi: 79.02 ; Count: 3 ... ``` 使用 `ls` 和 `cat` 命令查看文件系统中的文件和内容。`Data.txt` 文件存在且记录了多次温湿度数据,每次数据包含温度(Temp)、湿度(Humi)和计数(Count)。这些数据表明系统定期从传感器读取数据并写入文件。 ![day5物模型温湿度.png](https://oss-club.rt-thread.org/uploads/20240727/b9c36a398fa56078edfa31950c292c6f.png.webp) ![day5物模型温湿度图表.png](https://oss-club.rt-thread.org/uploads/20240727/df655b87f6607073f56439af13960372.png.webp) ### 总结 通过以上实验现象,我们可以看到设备成功连接Wi-Fi、启动温湿度传感器、连接MQTT服务器、处理MQTT消息并控制LED,同时将传感器数据定期记录到本地文件系统中。这一系列操作验证了系统的各个功能模块能够正常工作并协同运行。
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
luyism
用舍由时,行藏在我
文章
4
回答
0
被采纳
0
关注TA
发私信
相关文章
1
pandora添加aht10编译缺少头文件
2
draco开发板添加aht10软件包失败
3
请问是否遇到过aht10软件包使用后finsh如下报错?
4
添加aht10软件包,构建出现错误
5
终端出现The aht10 is under an abnormal status
6
为什么sensor_asair_aht10.c会报错,明明什么都没改呀
7
AHT10软解包添加后编译报错
8
无法使用AHT10传感器
9
RTT源代码使用Env增加AHT10后,编译报错?
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
机器人操作系统 (ROS2) 和 RT-Thread 通信
4
国产MCU移植系列教程汇总,欢迎查看!
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
ART-Pi
FinSH
USB
文件系统
DMA
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
ota在线升级
I2C_IIC
WIZnet_W5500
UART
flash
packages_软件包
cubemx
PWM
freemodbus
BSP
潘多拉开发板_Pandora
定时器
ADC
中断
flashDB
socket
Debug
GD32
编译报错
msh
keil_MDK
at_device
MicroPython
rt_mq_消息队列_msg_queue
ulog
C++_cpp
本月问答贡献
出出啊
1513
个答案
341
次被采纳
小小李sunny
1438
个答案
289
次被采纳
张世争
775
个答案
167
次被采纳
crystal266
546
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
2
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
2
次点赞
crystal266
2
篇文章
1
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部