diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/README.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/README.md" deleted file mode 100644 index b4dd9cd19371b1a40e7e932315edd9fb1702d8f7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/README.md" +++ /dev/null @@ -1 +0,0 @@ -# 第1组 \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/README.md" new file mode 100644 index 0000000000000000000000000000000000000000..684f197012e3bce6ec28bb146031696d30a436f3 --- /dev/null +++ "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/README.md" @@ -0,0 +1,27 @@ +# 作业1 论坛笔记 +[RT-Thread-【RSOC】软件包与组件入门RT-Thread问答社区 - RT-Thread](https://club.rt-thread.org/ask/article/338a5a73f4fb796d.html) +# 作业2 使用AHT10软件包采集温湿度并上传到emqx +## 运行截图 +![[ca0f6245a201499f33a303f3637b96d3.png]] +# 作业3 温湿度数据同步系统文件 +## 运行截图 +![[ea5ace48cb4c5c18e5899b70db084133.png]] +# 作业4 云端控制开发板上小灯翻转 +## 运行截图 +![[481b50ce039e77903e151603b97cb79c.png]] +# 作业代码 +## aht10读取温湿度代码 +[aht10_example.c](aht10_example.c) +## 文件操作相关代码 +[drv_filesystem.c](drv_filesystem.c) +## mqtt订阅发布相关代码 +[mqtt_test.c](mqtt_test.c) +# 作业 +- [x] 将今天的任务完成并将学习笔记上传至RT-Thread论坛,笔记标题命名为:【RSOC26】+ xxx +- [x] 使用AHT10软件包采集温湿度并上传到emqx(如果你有其他MQTT服务器也可上传到对应服务器上); +- [x] 梳理文件系统启动流程,熟悉shell命令使用。能够将font分区给挂载上。在温湿度上传上传云端的同时,将数据同步放在文件系统处,文件名为:Data.txt; + 文件内容: Temp:XX ; Humi:XX ; Count: 1(自上电起所采集的数据次数) + Temp:XX ; Humi:XX ; Count: 2(自上电起所采集的数据次数) + Temp:XX ; Humi:XX ; Count: 3(自上电起所采集的数据次数) +- [x] 利用云端给开发板发送指令然后实现小灯翻转; +- [x] 熟悉其他软件包 \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/aht10_example.c" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/aht10_example.c" new file mode 100644 index 0000000000000000000000000000000000000000..8e49364eba9bcf190cc768b8f2f8b28beabace11 --- /dev/null +++ "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/aht10_example.c" @@ -0,0 +1,40 @@ +#define DBG_TAG "AHT10" +#define DBG_LVL DBG_LOG +#include + +#include +#include + +float humi ,temp; + +int aht10_read_entry(void *par) +{ + aht10_device_t aht10 = aht10_init("i2c3"); + if(aht10 == RT_NULL) + { + LOG_E("aht10 init failed!\n"); + return -RT_ERROR; + } + + while(1) + { + temp = aht10_read_temperature(aht10); + humi = aht10_read_humidity(aht10); + + //rt_kprintf("temp = %.2f,humi = %.2f",temp,humi); + rt_thread_mdelay(3000); + } +} + +int aht10_thread_init(void) +{ + rt_thread_t aht10_thread = rt_thread_create("aht10", aht10_read_entry, RT_NULL, 1024, 10, 10); + if(aht10_thread == RT_NULL) + { + LOG_E("aht10 thread create failed!\n"); + return RT_ERROR; + } + rt_thread_startup(aht10_thread); + return RT_EOK; +} +MSH_CMD_EXPORT(aht10_thread_init,aht10_thread_init); diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/drv_filesystem.c" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/drv_filesystem.c" new file mode 100644 index 0000000000000000000000000000000000000000..0dc534b2833c9c257f08a80f508413e1ccc7ee4e --- /dev/null +++ "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/drv_filesystem.c" @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-12-13 balanceTWK add sdcard port file + * 2021-05-10 Meco Man fix a bug that cannot use fatfs in the main thread at starting up + * 2021-07-28 Meco Man implement romfs as the root filesystem + */ + +#include +#include +#include +#include + +#if DFS_FILESYSTEMS_MAX < 4 +#error "Please define DFS_FILESYSTEMS_MAX more than 4" +#endif +#if DFS_FILESYSTEM_TYPES_MAX < 4 +#error "Please define DFS_FILESYSTEM_TYPES_MAX more than 4" +#endif + +#define DBG_TAG "app.filesystem" +#define DBG_LVL DBG_INFO +#include + +#ifdef BSP_USING_FS_AUTO_MOUNT +#ifdef BSP_USING_SDCARD_FATFS + +static int onboard_sdcard_mount(void) +{ + if (dfs_mount("sd0", "/sdcard", "elm", 0, 0) == RT_EOK) + { + LOG_I("SD card mount to '/sdcard'"); + } + else + { + LOG_E("SD card mount to '/sdcard' failed!"); + } + + return RT_EOK; +} +#endif /* BSP_USING_SDCARD_FATFS */ +#endif /* BSP_USING_FS_AUTO_MOUNT */ + +#ifdef BSP_USING_FLASH_FS_AUTO_MOUNT +#ifdef BSP_USING_FLASH_FATFS +#define FS_PARTITION_NAME "filesystem" + +static int onboard_fal_mount(void) +{ + /* 初始化 fal 功能 */ + extern int fal_init(void); + extern struct rt_device *fal_blk_device_create(const char *parition_name); + fal_init(); + /* 在 spi flash 中名为 "filesystem" 的分区上创建一个块设备 */ + struct rt_device *flash_dev = fal_blk_device_create(FS_PARTITION_NAME); + if (flash_dev == NULL) + { + LOG_E("Can't create a block device on '%s' partition.", FS_PARTITION_NAME); + } + else + { + LOG_D("Create a block device on the %s partition of flash successful.", FS_PARTITION_NAME); + } + + /* 挂载 spi flash 中名为 "filesystem" 的分区上的文件系统 */ + if (dfs_mount(flash_dev->parent.name, "/fal", "elm", 0, 0) == 0) + { + LOG_I("Filesystem initialized!"); + } + else + { + LOG_E("Failed to initialize filesystem!"); + LOG_D("You should create a filesystem on the block device first!"); + } + + return RT_EOK; +} + +/* ==========新增font分区挂载函数,函数名独立:onboard_font_mount ========== */ +#define FONT_PARTITION_NAME "font" +static int onboard_font_mount(void) +{ + extern struct rt_device *fal_blk_device_create(const char *parition_name); + + struct rt_device *font_dev = fal_blk_device_create(FONT_PARTITION_NAME); + if (font_dev == NULL) + { + LOG_E("Can't create a block device on '%s' partition.", FONT_PARTITION_NAME); + return -RT_ERROR; + } + else + { + LOG_D("Create a block device on the %s partition of flash successful.", FONT_PARTITION_NAME); + } + + int ret = dfs_mount(font_dev->parent.name,"/font","elm",0,RT_NULL); + if(ret == RT_EOK) + { + LOG_I("font partition mount to '/font' ok!"); + } + else if(ret == -RT_EINVAL) + { + LOG_I("font partition already mounted."); + } + else + { + LOG_E("font mount failed! ret=%d, please format font partition(mkfs elmfat font)", ret); + } + return RT_EOK; +} + +#endif /*BSP_USING_FLASH_FATFS*/ +#endif /*BSP_USING_FLASH_FS_AUTO_MOUNT*/ + + +const struct romfs_dirent _romfs_root[] = +{ +#ifdef BSP_USING_SDCARD_FATFS + {ROMFS_DIRENT_DIR, "sdcard", RT_NULL, 0}, +#endif + +#ifdef BSP_USING_FLASH_FATFS + {ROMFS_DIRENT_DIR, "fal", RT_NULL, 0}, +#endif + /* 新增font虚拟目录项 */ + {ROMFS_DIRENT_DIR, "font", RT_NULL, 0}, +}; + +const struct romfs_dirent romfs_root = +{ + ROMFS_DIRENT_DIR, "/", (rt_uint8_t *)_romfs_root, sizeof(_romfs_root) / sizeof(_romfs_root[0]) +}; + +static int filesystem_mount(void) +{ + +#ifdef BSP_USING_FS + if (dfs_mount(RT_NULL, "/", "rom", 0, &(romfs_root)) != 0) + { + LOG_E("rom mount to '/' failed!"); + } + + /* 确保块设备注册成功之后再挂载文件系统 */ + rt_thread_delay(500); +#endif +#ifdef BSP_USING_FS_AUTO_MOUNT + onboard_sdcard_mount(); +#endif /* BSP_USING_FS_AUTO_MOUNT */ + + +#ifdef BSP_USING_FLASH_FS_AUTO_MOUNT + onboard_fal_mount(); + onboard_font_mount(); //调用font挂载函数 +#endif + + return RT_EOK; +} +INIT_APP_EXPORT(filesystem_mount); diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/481b50ce039e77903e151603b97cb79c.png" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/481b50ce039e77903e151603b97cb79c.png" new file mode 100644 index 0000000000000000000000000000000000000000..76733ede7c90a99b976752c453657432663ebc45 Binary files /dev/null and "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/481b50ce039e77903e151603b97cb79c.png" differ diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/ca0f6245a201499f33a303f3637b96d3.png" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/ca0f6245a201499f33a303f3637b96d3.png" new file mode 100644 index 0000000000000000000000000000000000000000..2a2463abcff05e6b0e1e0632abd0004ed6635c03 Binary files /dev/null and "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/ca0f6245a201499f33a303f3637b96d3.png" differ diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/ea5ace48cb4c5c18e5899b70db084133.png" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/ea5ace48cb4c5c18e5899b70db084133.png" new file mode 100644 index 0000000000000000000000000000000000000000..032e7bce23103b435a23d4435451bd53f561830f Binary files /dev/null and "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/figures/ea5ace48cb4c5c18e5899b70db084133.png" differ diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/mqtt_test.c" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/mqtt_test.c" new file mode 100644 index 0000000000000000000000000000000000000000..551ceeb4d8ac73666ebbd99e73b815fd80ebb477 --- /dev/null +++ "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/mqtt_test.c" @@ -0,0 +1,166 @@ + +/* + * @Author: jiejie + * @Github: https://github.com/jiejieTop + * @LastEditTime: 2020-06-17 14:35:29 + * @Description: the code belongs to jiejie, please keep the author information and source code according to the license. + */ +extern float temp; +extern float humi; + +#include +#include +#include +#include +#include +#include "mqttclient.h" + +#include + +//#include +#include +#include +#include /* rename() */ +#include +#include /* statfs() */ + +static uint32_t sample_count = 0; +static char file_buf[128]; +static char mqtt_buf[128]; + +#define PIN_LED_B GET_PIN(F, 11) // PF11 : LED_B --> LED +#define PIN_LED_R GET_PIN(F, 12) // PF12 : LED_R --> LED + + +#ifndef KAWAII_MQTT_HOST +#define KAWAII_MQTT_HOST "jiejie01.top" +#endif +#ifndef KAWAII_MQTT_PORT +#define KAWAII_MQTT_PORT "1883" +#endif +#ifndef KAWAII_MQTT_CLIENTID +#define KAWAII_MQTT_CLIENTID "rtthread001" +#endif +#ifndef KAWAII_MQTT_USERNAME +#define KAWAII_MQTT_USERNAME "rt-thread" +#endif +#ifndef KAWAII_MQTT_PASSWORD +#define KAWAII_MQTT_PASSWORD "rt-thread" +#endif +#ifndef KAWAII_MQTT_SUBTOPIC +#define KAWAII_MQTT_SUBTOPIC "rtt-sub" +#endif +#ifndef KAWAII_MQTT_PUBTOPIC +#define KAWAII_MQTT_PUBTOPIC "rtt-pub" +#endif + +char send_buf[128]; + +static void sub_topic_handle1(void* client, message_data_t* msg) +{ + (void) client; + KAWAII_MQTT_LOG_I("-----------------------------------------------------------------------------------"); + KAWAII_MQTT_LOG_I("%s:%d %s()...\ntopic: %s\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload); + KAWAII_MQTT_LOG_I("-----------------------------------------------------------------------------------"); + + //解析指令控制小灯 + char *rcvd_cmd = (char*)msg->message->payload; + if(rt_strcmp(rcvd_cmd, "led_toggle") == 0) + { + rt_kprintf("toggle LED!\n"); + rt_pin_write(PIN_LED_R, !rt_pin_read(PIN_LED_R)); + } + else if(rt_strcmp(rcvd_cmd, "led_on") == 0) + { + rt_kprintf("LED On!\n"); + rt_pin_write(PIN_LED_R, PIN_LOW); + } + else if(rt_strcmp(rcvd_cmd, "led_off") == 0) + { + rt_kprintf("LED Off!\n"); + rt_pin_write(PIN_LED_R, PIN_HIGH); + } +} + +void save_data_to_file(float temp, float humi, uint32_t count) +{ + int fd; + int w_len; + + fd = open("/font/Data.txt", O_WRONLY | O_CREAT | O_APPEND, 0666); + if(fd < 0) + { + rt_kprintf("open /font/Data.txt FAILED!\n"); + return; + } + + w_len = rt_snprintf(file_buf, sizeof(file_buf),"Temp: %.2f ; Humi: %.2f ; Count: %d\r\n", temp, humi, count); + write(fd, file_buf, w_len); + close(fd); +} + + +static int mqtt_publish_handle1(mqtt_client_t *client) +{ + sample_count ++; + + mqtt_message_t msg; + memset(&msg, 0, sizeof(msg)); + + rt_snprintf(send_buf, sizeof(send_buf), "temp = %.2f,humi = %.2f\n", temp, humi); + msg.payload = (void *)send_buf; + + msg.qos = QOS0; + + mqtt_publish(client, KAWAII_MQTT_PUBTOPIC, &msg); + save_data_to_file(temp, humi, sample_count); + + return RT_EOK; +} + +static void kawaii_mqtt_demo(void *parameter) +{ + mqtt_client_t *client = NULL; + + rt_thread_delay(6000); + + + + mqtt_log_init(); + + client = mqtt_lease(); + + mqtt_set_host(client, KAWAII_MQTT_HOST); + mqtt_set_port(client, KAWAII_MQTT_PORT); + mqtt_set_user_name(client, KAWAII_MQTT_USERNAME); + mqtt_set_password(client, KAWAII_MQTT_PASSWORD); + mqtt_set_client_id(client, KAWAII_MQTT_CLIENTID); + mqtt_set_clean_session(client, 1); + + KAWAII_MQTT_LOG_I("The ID of the Kawaii client is: %s ", KAWAII_MQTT_CLIENTID); + + mqtt_connect(client); + + mqtt_subscribe(client, KAWAII_MQTT_SUBTOPIC, QOS0, sub_topic_handle1); + + while (1) { + mqtt_publish_handle1(client); + + mqtt_sleep_ms(4 * 1000); + } +} + +int ka_mqtt(void) +{ + rt_thread_t tid_mqtt; + + tid_mqtt = rt_thread_create("kawaii_demo", kawaii_mqtt_demo, RT_NULL, 4096, 17, 10); + if (tid_mqtt == RT_NULL) { + return -RT_ERROR; + } + + rt_thread_startup(tid_mqtt); + + return RT_EOK; +} +MSH_CMD_EXPORT(ka_mqtt, Kawaii MQTT client test program); diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..1be0fa4fd7886e7836ce596c2d764457814758fa --- /dev/null +++ "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\347\254\224\350\256\260.md" @@ -0,0 +1,451 @@ +# 软件包与组件 +本文以 RT-Thread 为基础,记录软件包、网络连接、文件系统和 Flash 抽象层的学习路径。示例中的板卡、I2C 总线名、Flash 设备名和 Kconfig 菜单项可能因 BSP 版本不同而变化,实际操作时应以工程中的配置和 `list_device` 输出为准。 +## 软件包 +- 软件包:具有特定功能、用于完成特定任务的一个程序或一组程序。 +- 软件包通常已经封装**了底层驱动、协议栈或通用算法**,应用程序只需要**调用它提供的 API,就可以减少重复开发。** +- 使用软件包的**主要收益**: + - 缩短开发周期,快速验证传感器、网络和存储功能; + - 复用经过测试的驱动和协议实现; + - 通过 Kconfig 和 `menuconfig` 按需裁剪功能,控制 ROM、RAM 和线程栈占用。 +- **RT-Thread 软件包中心:[软件包中心](https://packages.rt-thread.org/index.html)** +- 在 RT-Thread Studio 中,ENV/Kconfig 工具通常位于 Studio 安装目录下的 `env` 目录。不同版本的 Studio 菜单名称可能略有差异,但基本流程都是“打开配置、保存配置、生成工程、下载软件包”。 +### 温湿度传感器:AHT10(I2C 设备) +#### I2C传感器驱动流程: +- 驱动一个 I2C 传感器通常需要完成的工作: + 1. 阅读数据手册,确认**设备地址、初始化命令、测量命令和数据格式**; + 2. 找到设备**实际连接的 I2C 总线**,并确认 BSP 已经**注册该总线设备**; + 3. 使用 RT-Thread I2C API 完成**写命令、读数据和错误处理**; + 4. 将原始数据转换为**温度、湿度等应用层单位**,并通过线程或 Shell 命令验证。 +**不使用软件包**的驱动示例:[AHT10NotUsePackage.c](./Docx/AHT10NotUsePackage.c)。 + 该示例直接调用 `rt_i2c_transfer()`,适合学习 I2C 消息结构和传感器时序。 +#### 直接使用 I2C API +原始驱动至少要关注的问题: +- `AHT_I2C_BUS_NAME` 必须与板卡实际**注册的总线名**一致; +- **设备地址**通常为 `0x38`,但仍应以传感器数据手册和硬件连接为准; +- 发送测量命令后需要**等待转换完成**,再读取状态字节和数据字节; +- `rt_i2c_transfer()` 的**返回值应检查**,不能只依赖串口输出判断成功; +- **定点或浮点输出**需要相应的 `rt_kprintf` 配置,资源受限时可以先按整数和小数部分输出。 +#### 使用 AHT10 软件包 +1. 在板级配置中开启对应的 I2C 外设和 AHT 传感器引脚; +2. 在软件包菜单中启用 AHT10 软件包; +3. 在工程中确认 `aht10.h` 能够被包含,并将总线名改成实际值,例如 `i2c3`; +4. 如果需要直接打印 `%f`,再启用 `rt_vsprintf_full` 软件包;否则继续使用整数和小数部分输出; +5. 执行 `pkgs --update` 下载软件包并重新生成工程。 +**使用软件包的示例见:[AHT10UsePackage.c](./Docx/AHT10UsePackage.c)。** +#### 验证与故障排查 +启动示例线程后,可以观察类似下面的输出: +```c +msh > AHT10_Creat_Thread +Humi: xxx +Temp: xxx +``` +如果**没有输出或数值明显异常**: + +| 现象 | 检查项 | +| :--------: | :----------------------------------: | +| 找不到 I2C 总线 | Kconfig 是否开启 I2C、总线名是否与宏定义一致 | +| 读写返回错误 | SDA/SCL 接线、上拉电阻、设备地址和供电电压 | +| 温湿度一直为零 | 是否完成初始化和校准,测量命令后等待时间是否足够 | +| `%f` 输出异常 | 是否启用 `rt_vsprintf_full`,或改用整数/小数拆分打印 | + +### MQTT 协议 +#### 基本概念 +- MQTT(Message Queuing Telemetry Transport)是一种轻量级的**发布/订阅消息协议**,适合资源受限设备以及带宽有限、延迟较高或连接不稳定的网络环境。 +- MQTT 通信由**客户端**、**服务端**、**主题**和**消息**组成: + - **Client:** 客户端,即设备或应用程序。客户端可以连接服务端、发布消息、订阅主题、取消订阅和断开连接。 + - **Server:** 服务端,也称 Broker,负责维护连接、接收发布消息、处理订阅并将消息转发给匹配的客户端。 + - **Topic Name:** 主题名,用于对消息进行分层分类,例如 `rsoc/sensor/temperature`。 + - **Publish:** 发布,将消息发送到指定主题。 + - **Subscription:** 订阅,声明客户端希望接收哪些主题的消息。 +- MQTT 的优势: + - **轻量级:** 报文开销小,适合 RAM、Flash 和功耗受限的嵌入式设备; + - **解耦:** 发布者和订阅者不需要直接知道对方,只需要约定主题; + - **可扩展:** 可以通过主题层级和通配符组织大量设备和数据; + - **可配置可靠性:** 通过 QoS、保活和会话参数在实时性、带宽与可靠性之间取舍。 +#### QoS、主题和会话 +- MQTT 定义了三个 QoS 等级。 +- QoS 并不是“网络绝对可靠”的保证,而是客户端与 Broker 之间的交付语义: + +| 等级 | 含义 | 典型场景 | +| ----- | ----------------- | --------------- | +| QoS 0 | 最多一次,不等待确认,可能丢失 | 高频环境数据、可丢失的状态刷新 | +| QoS 1 | 至少一次,可能重复,需要应用层去重 | 报警、控制命令和重要状态 | +| QoS 2 | 恰好一次,交互次数更多,开销最高 | 不能重复处理且消息量较小的业务 | + +主题可以使用 `+` 匹配单层,使用 `#` 匹配多层。例如,订阅 `rsoc/+/temperature` 可以接收不同设备的一层温度主题。生产项目中应为不同设备规划清晰的主题层级,并对发布和订阅权限进行限制。 +#### RT-Thread 上使用 MQTT +本实验使用 [EMQX 免费公共 MQTT Broker](https://www.emqx.com/zh/mqtt/public-mqtt5-broker): +- 只采用**未加密 TCP 连接** +- 当前公共接入地址为 `broker.emqx.io` +- TCP 端口为 `1883` +- 不需要用户名和密码 +- 该服务只适合学习和功能验证,不能用于生产环境。 +#### 获取引脚编号 +```txt +pin [option] + num: get pin number from hardware pin + num can be PE02, PE2, PE.02, PE.2, pe02, pe2, pe.02, pe.2 + e.g. MSH >pin num PA.16 + mode: set pin mode to output/input/input_pullup/input_pulldown/output_od + e.g. MSH >pin mode PA.16 output + read: read pin level of hardware pin + e.g. MSH >pin read PA.16 + write: write pin level(high/low or on/off) to hardware pin + e.g. MSH >pin write PA.16 high + help: this help list +msh >pin num PG.11 +PG.11 : 107 +``` + +| 引脚名称 | 引脚编号 | +| ------------------------------------- | ---- | +| RW007 BUS NAME | spi2 | +| CS pin index | 90 | +| BOOT0 pin index (same as spi clk pin) | 29 | +| BOOT1 pin index (same as spi cs pin) | 90 | +| INT(BUSY) pin index | 107 | +| RESET pin index | 111 | +#### 调试顺序 +1. 开启 RW007 或其他网络模组,确认网卡能够获取 IP; +2. 使用 `ifconfig`、`ping` 等命令检查网络、网关和 DNS; +3. 在 `RT-Thread online packages` 中启用 `kawaii-mqtt` 软件包; +4. 将 MQTT 版本设为 `4`,即 MQTT 3.1.1; +5. 将 Broker 和端口分别设为 `broker.emqx.io` 和 `1883`,用户名、密码留空; +6. 为板卡设置唯一的客户端 ID,并配置独立的发布和订阅主题; +7. 先完成 MQTTX 与板卡之间的双向收发,再接入传感器数据; +8. 为断线重连、心跳超时、发布失败和接收回调增加日志。 +>注: +> 公共 Broker 上的主题和消息对其他用户可见。 +> 主题不要使用 `test`、`hello` 等常见名称,建议加入学号、板卡编号或随机后缀 +```text +板卡发布,MQTTX 订阅:rsoc/<唯一标识>/device/up +MQTTX 发布,板卡订阅:rsoc/<唯一标识>/device/down +``` +#### 使用 MQTTX 验证收发 +[MQTTX](https://mqttx.app/zh/downloads) 是跨平台 MQTT 客户端,可以在电脑上查看板卡发布的消息,也可以向板卡订阅的主题发送测试数据。 +##### 新建 MQTTX 连接时的参数 + +| 配置项 | 值 | +| ------------ | ------------------------------ | +| 协议 | `mqtt://`(TCP) | +| Host | `broker.emqx.io` | +| Port | `1883` | +| MQTT Version | `3.1.1`,与当前 `kawaii-mqtt` 配置一致 | +| Client ID | 自定义唯一值,例如 `mqttx-rsoc-<唯一标识>` | +| Username | 随便填 | +| Password | 随便填 | + +##### 连接成功后,验证顺序: +1. MQTTX 订阅 `rsoc/<唯一标识>/device/up`; +2. 在板卡上发布一条消息,确认 MQTTX 能显示主题、QoS 和 Payload; +3. MQTTX 向 `rsoc/<唯一标识>/device/down` 发布一条短消息; +4. 确认板卡订阅回调收到该消息,并正确使用消息长度; +5. 断开板卡网络后重新连接,确认订阅可以自动恢复; +6. 再接入 AHT10 数据,建议使用简短 JSON,例如 `{"temperature":26.1,"humidity":45.2}`。 +>如果 MQTTX 能连接但收不到数据,先核对两端的主题方向:板卡的发布主题必须等于 MQTTX 的订阅主题,板卡的订阅主题必须等于 MQTTX 的发布主题。主题区分大小写,也不要多写或漏写 `/`。 +## 组件(Component) +- 组件是可以**独立开发、测试、部署和维护**的**软件单元**。 +- 与单个驱动相比,组件通常包含**更完整的接口、配置项和示例**,能够为上层应用提供稳定的功能边界。 +### 文件系统 +- 本节基于板载 W25Q64,学习 RT-Thread DFS、FAL 和 POSIX 接口之间的关系。 +- 参考文档:[RT-Thread 文件系统](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/filesystem/filesystem)。 +#### 文件系统定义 +- DFS(Device File System)是 RT-Thread 提供的虚拟文件系统组件。 +- 它向上层提供统一的路径和文件操作接口,向下层对接 FatFS、RomFS、DevFS 等不同文件系统以及块设备。 +#### 文件系统架构 +在 DFS 中,系统使用统一的根目录 `/`。一个具体文件系统通常先格式化到块设备,再挂载到某个目录,例如 `/fal`;挂载完成后,应用不需要关心底层 Flash 的擦写细节,只需要按路径访问文件。 +#### 常见文件系统 + +| 类型 | 特点 | +| --- | --- | +| FatFS | 兼容 FAT 格式,移植性较好,适合 SD 卡和可读写 Flash;本实验中的 `elm_fat` 属于这一类。 | +| RomFS | 结构简单、只读,适合作为固件内置资源或只读根文件系统;不适合频繁写入。 | +| DevFS | 将设备映射到 `/dev` 目录,使设备可以使用类似文件的 `read`、`write` 接口。 | +| UFFS | 面向 NAND Flash 的轻量级文件系统,适合资源受限场景;使用前应确认 BSP 和维护状态。 | +| NFS | 通过网络共享主机文件系统,适合开发调试,不建议直接作为量产设备的本地存储方案。 | + +#### POSIX 接口层 +POSIX(Portable Operating System Interface)为应用提供统一的文件和目录操作接口。在 RT-- Thread 中,常见接口包括: + - 文件:`open()`、`close()`、`read()`、`write()`、`lseek()`、`rename()`、`stat()`、`unlink()`; + - 目录:`mkdir()`、`opendir()`、`readdir()`、`closedir()`; + - 文件系统:`mkfs()`、`mount()`、`umount()`。 +- 文件描述符 `fd` 是一个整数句柄,代表当前进程打开的文件或设备。调用 `open()` 成功后必须在所有退出路径调用 `close()`,否则长时间运行可能耗尽文件描述符。 + +| API | 描述 | +| --- | --- | +| `int rename(const char *old, const char *new)` | 文件重命名 | +| `int stat(const char *file, struct stat *buf)` | 获取文件状态 | +| `int unlink(const char *pathname)` | 删除文件 | + +#### 目录管理 +目录操作应遵循**“打开、遍历、关闭”的生命周期**。 +- `readdir()` 返回 `RT_NULL` 表示遍历结束 +- 异常路径要调用 `closedir()`。 +#### 文件系统启动流程 +1. **DFS 组件初始化:** 系统启动时初始化文件系统注册表、文件描述符表以及相关锁。如果启用了 DevFS,还会初始化设备文件系统。 +2. **注册文件系统类型:** FatFS、RomFS 等文件系统通过各自的初始化函数注册到 DFS,之后才能被 `mount()` 识别。 +3. **格式化并挂载:** 块设备第一次使用时需要选择文件系统并格式化,然后挂载到路径。自动挂载功能可以在系统启动阶段完成这一步,但量产项目仍应检查挂载返回值和分区版本。 +### FAL(搭配 SFUD 使用) +#### SFUD +- [SFUD](https://github.com/armink/SFUD)(Serial Flash Universal Driver)是串行 Flash 通用驱动库。 +- 它屏蔽不同 SPI NOR Flash 在 JEDEC ID、读写和擦除命令上的差异,为上层提供统一的设备访问接口,例如: + - `sfud_get_device()` + - `sfud_read()` + - `sfud_erase()` + - `sfud_write()`。 +#### FAL +- FAL(Flash Abstraction Layer)是 **Flash 抽象层**,负责**管理 Flash 设备和分区**,并向文件系统、OTA、NVM 等上层模块**提供统一的分区访问接口**。 +- FAL 与 SFUD 的职责: + - `SFUD` 负责“如何和具体 Flash 芯片通信”; + - `FAL` 负责“如何把一个或多个 Flash 划分成可管理的逻辑分区”。 +- FAL 的**常见特性**: + - 支持**静态配置分区表**,并**可关联多个 Flash 设备**; + - 分区表**支持自动装载**,减少多固件项目中的重复定义; + - **对操作系统依赖较少**,也可以运行在 Bootloader 或裸机环境; + - 向文件系统、OTA、NVM 等组件**提供统一的分区接口**; + - 提供基于 FinSH/MSH 的**测试命令**,便于按地址读、写和擦除 Flash。 +#### FAL API +FAL API 的典型使用顺序是:找到分区、读取分区信息、按偏移读写或擦除。执行擦除和写入前,应确认偏移地址、长度和对齐要求,避免覆盖其他分区。 +### 文件系统结合 FAL 配置 W25Q64 +#### 设备链路 +一个常见的 W25Q64 数据链路: +```text +SPI 控制器 +  -> spi20 设备 +  -> SFUD SPI_FLASH 设备(例如 W25Q64) +  -> FAL 分区 +  -> BLK 块设备 +  -> FatFS/elm_fat +  -> DFS 挂载点(例如 /fal) +  -> POSIX 文件 API +``` + +在 BSP 的 `rt_hw_spi_flash()` 中,通常: +1. 把 SPI 设备挂到总线上 +2. 通过 `rt_sfud_flash_probe()` 绑定为 `SPI_FLASH` 设备 +3. 在 FAL 中定义分区,并为目标分区创建块设备 +4. 文件系统格式化并挂载后,应用即可通过 `open()`、`read()`、`write()` 等接口访问 W25Q64。 +#### menuconfig 配置步骤 +1. 开启 SPI Flash、文件系统和 FAL 相关**外设**。 +2. 在文件系统菜单中**启用基于 W25Q64 的 FAL 分区和自动挂载**。 +3. 确认 **FAL 分区配置**已经启用。 +4. 检查**自动加载或自动挂载选项**,并保存配置。 +5. 如果 BSP 还有额外的存储或兼容选项,再根据工程需求开启,**不要无条件勾选全部功能**。 +#### Shell 验证 +下载运行后,先确认设备和分区名称: +```c +msh > list_device +msh > fal probe +msh > ls / +``` + +如果没有启用自动挂载,可以根据 BSP 实际设备名手动格式化和挂载: +```c +msh > mkfs -t elm +msh > mount /fal +msh > ls /fal +``` + +命令中的 `` 必须替换为 `list_device` 输出的真实块设备名。格式化会清除分区中的原有数据,首次操作前应确认分区范围。 +#### POSIX 文件读写示例 +- 写入、读取和目录遍历 +- `/fal` 必须替换成工程实际的挂载点。 +```c +#include +#include +#include + +static const char message[] = "Hello, RT-Thread. Welcome to RSOC!"; + +static char buffer[sizeof(message)] = {0}; + +static void FileSystem_Test(void) + +{ +    int fd; +    int written; +    int size; + + +    fd = open("/fal/FileTest.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); +    if (fd < 0) +    { +        rt_kprintf("open for write failed\n"); +        return; +    } + + +    written = write(fd, message, sizeof(message) - 1); +    close(fd); +    if (written != (int)(sizeof(message) - 1)) +    { +        rt_kprintf("write failed: %d\n", written); +        return; +    } + + +    fd = open("/fal/FileTest.txt", O_RDONLY, 0); +    if (fd < 0) +    { +        rt_kprintf("open for read failed\n"); +        return; +    } + +    size = read(fd, buffer, sizeof(buffer) - 1); +    close(fd); +    if (size < 0) +    { +        rt_kprintf("read failed: %d\n", size); +        return; +    } + + +    buffer[size] = '\0'; +    rt_kprintf("read: %s\n", buffer); +} + +MSH_CMD_EXPORT(FileSystem_Test, file read/write test); + +static void readdir_sample(void) + +{ +    DIR *dirp = opendir("/fal"); +    struct dirent *d; + + +    if (dirp == RT_NULL) +    { +        rt_kprintf("open directory failed\n"); +        return; +    } + +    while ((d = readdir(dirp)) != RT_NULL) +    { +        rt_kprintf("found %s\n", d->d_name); +    } +    closedir(dirp); +} + +MSH_CMD_EXPORT(readdir_sample, list files under /fal); + +``` + +- 执行 `FileSystem_Test` 后,应该能够看到写入和读取的字符串一致。 +- 原示例中把 `read()` 返回值保存到无符号类型再判断 `< 0`,无法正确识别读取错误;同时没有为读取结果补充字符串结束符,这里已经一并修正。 +#### 文件系统故障排查 + +| 现象 | 检查顺序 | +| --- | --- | +| 找不到 W25Q64 | SPI 引脚、片选、设备 ID、SFUD 探测日志 | +| FAL 分区不存在 | 分区表是否编译进工程,设备名是否与 FAL 配置一致 | +| `mount` 失败 | 分区是否已格式化、文件系统类型是否匹配、挂载点是否存在 | +| 文件写入失败 | 分区是否只读、剩余空间是否足够、擦写对齐是否满足要求 | +| 重启后文件丢失 | 是否启用自动挂载,是否在卸载或掉电前完成写入同步 | +# tips +## aht10 +### 使能 +- `MSH: command shell` 使能终端 +- `使能ulog` 打印日志 +- `Enable AHT21 (i2c3)` 使能AHT10 +- `rt_vsnprintf_full: fully functional version of rt_vsnprintf` 浮点数输出 +### 头文件与宏定义 +```c +#define DBG_TAG "AHT10" +#define DBG_LVL DBG_LOG +#include + +#include +#include +``` +### 函数 +#### aht10_read_entry +```c +int aht10_read_entry(void *par) +{ + aht10_device_t aht10 = aht10_init("i2c2"); + if(aht10 == RT_NULL) + { + LOG_E("aht10 init failed!\n"); + return -RT_ERROR; + } + + while(1) + { + float temp = aht10_read_temperature(aht10); + float humi = aht10_read_humidity(aht10); + + rt_kputs("temp = %.2f,humi = %.2f",temp,humi); + rt_thread_mdelay(3000); + } +} +``` +#### aht10_thread_init +```c +int aht10_thread_init(void) +{ + rt_thread_t aht10_thread = rt_thread_create("aht10", aht10_read_entry, RT_NULL, 1024, 10, 10); + if(aht10_thread == RT_NULL) + { + LOG_E("aht10 thread create failed!\n"); + return RT_ERROR; + } + rt_thread_startup(aht10_thread); + return RT_EOK; +} +``` +#### msh +`MSH_CMD_EXPORT(aht10_thread_init,aht10_thread_init);` +## mqtt +### 使能 +- `rw007: SPI WIFI rw007 driver` WIFI + +| 配置项 | 值 | +| ------------------------------------- | ---- | +| RW007 BUS NAME | spi2 | +| CS pin index | 90 | +| BOOT0 pin index (same as spi clk pin) | 29 | +| BOOT1 pin index (same as spi cs pin) | 90 | +| INT/BUSY pin index | 107 | +| RESET pin index | 111 | +- `kawaii-mqtt: a mqtt client based on the socket API, support QoS2, mbedtls.` + - `enable kawaii mqtt test` + +| 配置项 | 值 | +| -------------------- | -------------- | +| mqtt host | broker.emqx.io | +| mqtt port | 1883 | +| mqtt client id | rtthread_7730 | +| mqtt username | rt‑thread | +| mqtt password | rt‑thread | +| mqtt subscribe topic | rtt‑pub_7730 | +| mqtt publish topic | rtt‑pub_7730 | + +## fal文件系统 + +| 分区名 (name) | 存储设备 (flash_dev) | 起始偏移 (offset) | 分区大小 (length) | 存储内容说明 | +| -------------- | ----------------- | ------------- | ------------------- | ------------------------------------------------------------------------------- | +| **app** | onchip_flash_128k | 0x00000000 | 0x00060000 (384 KB) | **应用程序分区**:存放 STM32 主程序固件(用户编译生成的 bin/hex 烧录到此),是程序运行的代码区。 | +| **param** | onchip_flash_128k | 0x00060000 | 0x000a0000 (640 KB) | **参数分区**:存放系统运行参数、配置信息(如 WiFi 账号密码、MQTT 配置、设备参数等持久化数据)。 | +| **easyflash** | W25Q64 | 0x00000000 | 0x00080000 (512 KB) | **EasyFlash KV 存储分区**:基于 EasyFlash 组件的键值对存储区,存放环境变量、用户配置、运行状态等可快速读写的小数据。 | +| **download** | W25Q64 | 0x00080000 | 0x00100000 (1 MB) | **OTA 下载 / 升级缓存分区**:存放通过网络下载的待升级固件包,Bootloader 升级时从此区读取新固件并搬运到 app 区。 | +| **wifi_image** | W25Q64 | 0x00180000 | 0x00080000 (512 KB) | **WiFi 固件镜像分区**:存放 RW007 等外置 WiFi 模块的固件镜像,模块启动或升级时从此分区加载 WiFi 协议栈固件。 | +| **font** | W25Q64 | 0x00200000 | 0x00300000 (3 MB) | **字库分区**:存放 OLED/LCD 显示所需的字体文件(如你之前报错的 `/font/Data.txt` 字库数据),供中文 / ASCII 显示调用。 | +| **filesystem** | W25Q64 | 0x00500000 | 0x00300000 (3 MB) | **文件系统分区**:挂载 FatFs/elm‑chan 文件系统,存放用户数据文件(日志、图片、配置文件等),对应你之前 `ls /` 看到的可操作目录。 | +### 使能 +- `Enable File System` 使能文件系统 +- `Enable FAL filesystem partition based on W25Q64` 使用自动挂载 +- 旧版本兼容性支持 +- 使用设备虚拟文件系统 + - 使能 elm‑chan FatFs + - 设置要处理的最大扇区大小——4096 +- `Enable SDCARD (FATFS)` +- `使能POSIX文件系统` +### 找不到头文件dfs_posix.h +换成: +```c +#include +#include +#include /* rename() */ +#include +#include /* statfs() */ +``` \ No newline at end of file