diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/homework.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/homework.md" new file mode 100644 index 0000000000000000000000000000000000000000..b83c53884bee59137035ee33dfd7d05cc06958d8 --- /dev/null +++ "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/homework.md" @@ -0,0 +1,17 @@ +# Day5 作业说明 + +本目录提交 Day5 的完整实验材料: + +- `notes.md`:学习笔记、文件系统流程、测试结果与图片; +- `rsoc26_iot.c`:AHT10/AHT21、文件系统、Wi-Fi、MQTT 和控灯应用代码; +- `images/`:MQTTX 控制界面与开发板运行状态照片。 + +## 使用说明 + +1. 在本地工程中填写 Wi-Fi 占位符; +2. 挂载分区:`mount filesyst /fal elm`; +3. 启动 MQTT:`mqtt_start <用户名> <密码>`; +4. MQTTX 向主题 `123` 发送 `toggle` 或 `{"msg":"toggle"}`; +5. 红灯翻转,串口输出 `MQTT command: LED toggled`。 + +> 公开代码已用占位符替换 Wi-Fi 凭据,未提交任何密码或云端私密信息。 diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/led-after.jpg" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/led-after.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..76307f9fda3f59ba7cf541a1def316b3244e56bb Binary files /dev/null and "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/led-after.jpg" differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/led-before.jpg" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/led-before.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..7b557ffb0ab94354b6628f48e11a530dd4acdc9f Binary files /dev/null and "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/led-before.jpg" differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/mqttx-control.png" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/mqttx-control.png" new file mode 100644 index 0000000000000000000000000000000000000000..03655df47452ffd80ac318e55c6d35bc984f2090 Binary files /dev/null and "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/images/mqttx-control.png" differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/notes.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/notes.md" new file mode 100644 index 0000000000000000000000000000000000000000..8aa96bde3131e330bef51a2df891877f3a736a44 --- /dev/null +++ "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/notes.md" @@ -0,0 +1,67 @@ +# 【RSOC26】Day5 AHT10、文件系统与 MQTT 实践笔记 + +姓名:杨智文 +组别:第3组 + +## 1. 完成内容 + +本次实验基于 RT-Spark(STM32F407)开发板完成了温湿度采集、FAL 文件系统写入、EMQX MQTT 上报与云端控灯。 + +- 使用板载 AHT21(兼容 AHT10 协议)通过 `i2c3` 读取温度、湿度; +- 将每次采集结果追加写入 `/fal/Data.txt`; +- 使用 RW007 连接 Wi-Fi,并通过 Paho MQTT 连接 EMQX; +- 主题 `123` 同时用于数据上报与控制,兼容纯文本 `toggle` 和 JSON `{"msg":"toggle"}`; +- 接到控制指令后翻转 PF12 板载红灯。 + +## 2. 文件系统启动流程 + +1. FAL 根据分区表创建块设备; +2. `filesystem` 分区以 Elm-Fat 格式化,设备名为 `filesyst`; +3. 使用 `mount filesyst /fal elm` 挂载到 `/fal`; +4. 应用以 `O_CREAT | O_APPEND` 打开 `/fal/Data.txt` 并写入采样记录。 + +`font` 是独立的资源分区,挂载失败时不能格式化,否则会破坏原有字库资源。 + +## 3. Shell 常用命令 + +```text +ifconfig # 查看网络 IP +mount filesyst /fal elm # 挂载文件系统 +cat /fal/Data.txt # 查看采样数据 +mqtt_start # 启动 MQTT 客户端 +rsoc26_sample # 采样、写文件并上报 +cloud_toggle # 通过 MQTT 发布一条 toggle 测试命令 +``` + +## 4. 实测结果 + +Data.txt 实测内容: + +```text +Temp: 30.2 ; Humi: 61.6 ; Count: 1 +Temp: 29.1 ; Humi: 65.5 ; Count: 2 +Temp: 29.2 ; Humi: 65.6 ; Count: 3 +``` + +MQTT 日志确认:服务器连接成功、主题 `123` 订阅成功;MQTTX 发布 `{"msg":"toggle"}` 后,串口输出 `MQTT command: LED toggled`。 + +## 5. 验证图片 + +![MQTTX 主题 123 控制消息](images/mqttx-control.png) + +图 1:MQTTX 向主题 `123` 发布 `toggle` 控制消息。 + +![开发板控灯状态一](images/led-before.jpg) + +图 2:RT-Spark 开发板运行状态。 + +![开发板控灯状态二](images/led-after.jpg) + +图 3:云端指令到达后,板载红灯翻转。 + +## 6. 其他软件包认识 + +- `aht10`:AHT10/AHT20/AHT21 温湿度传感器驱动; +- `pahomqtt`:RT-Thread 的 MQTT 客户端软件包; +- `RW007`:Wi-Fi 模块驱动与网络接入; +- `FAL`:统一管理片上 Flash 和外部 Flash 分区。 diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/rsoc26_iot.c" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/rsoc26_iot.c" new file mode 100644 index 0000000000000000000000000000000000000000..04d300e1533621edc04f3dfa6e5c73282f5f3063 --- /dev/null +++ "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/rsoc26_iot.c" @@ -0,0 +1,269 @@ +/* + * RSOC Day5: Spark-1 AHT21(AHT10 compatible) + MQTT + filesystem demo. + * + * Hardware: AHT21 is connected to the board's software I2C3 bus + * (PE0=SCL, PE1=SDA). PF12 drives the on-board red LED. + * + * Credentials are deliberately accepted by the shell command at runtime; + * do not put Wi-Fi or cloud passwords in source code or a public repository. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "aht10.h" +#include "paho_mqtt.h" + +#define RSOC26_I2C_BUS "i2c3" +/* GPIOF is port index 5, so PF12 maps to pin number 5 * 16 + 12. */ +#define RSOC26_LED_PIN 92 +#define RSOC26_DATA_FILE "/fal/Data.txt" +#define RSOC26_MQTT_URI "tcp://de1ea5cf.ala.dedicated.aliyun.emqxcloud.cn:1883" +#define RSOC26_PUB_TOPIC "123" +#define RSOC26_CMD_TOPIC "123" + +/* Local lab Wi-Fi. Do not copy these credentials into the public notes. */ +#define RSOC26_WIFI_SSID "" +#define RSOC26_WIFI_PASSWORD "" + +static aht10_device_t aht_dev; +static MQTTClient mqtt_client; +static rt_uint32_t sample_count; +static rt_bool_t mqtt_started; + +/* The RW007 package exports this board-port initializer. Providing an MSH + * wrapper also makes initialization observable and retryable during lab work. */ +extern int wifi_spi_device_init(void); + +static void rsoc26_led_toggle(void) +{ + rt_pin_write(RSOC26_LED_PIN, !rt_pin_read(RSOC26_LED_PIN)); +} + +static void rsoc26_message_callback(MQTTClient *client, MessageData *msg_data) +{ + /* Enough room for MQTTX-formatted JSON command payloads. */ + char command[96]; + int length = msg_data->message->payloadlen; + + RT_UNUSED(client); + if (length >= (int)sizeof(command)) + length = sizeof(command) - 1; + + rt_memcpy(command, msg_data->message->payload, length); + command[length] = '\0'; + + /* MQTTX may send either plain "toggle" or JSON {"msg":"toggle"}. */ + if (!rt_strcmp(command, "toggle") || rt_strstr(command, "toggle") != RT_NULL) + { + rsoc26_led_toggle(); + rt_kprintf("[rsoc26] MQTT command: LED toggled\n"); + } + else + { + rt_kprintf("[rsoc26] unsupported command: %s\n", command); + } +} + +static int rsoc26_sensor_init(void) +{ + if (aht_dev != RT_NULL) + return RT_EOK; + + aht_dev = aht10_init(RSOC26_I2C_BUS); + if (aht_dev == RT_NULL) + { + rt_kprintf("[rsoc26] AHT21 init failed: check %s (PE0/PE1)\n", RSOC26_I2C_BUS); + return -RT_ERROR; + } + return RT_EOK; +} + +static int rsoc26_append_data(float temp, float humi) +{ + char line[96]; + int fd; + int length; + + length = rt_snprintf(line, sizeof(line), + "Temp: %d.%d ; Humi: %d.%d ; Count: %d\r\n", + (int)temp, (int)(temp * 10) % 10, + (int)humi, (int)(humi * 10) % 10, + sample_count); + fd = open(RSOC26_DATA_FILE, O_WRONLY | O_CREAT | O_APPEND, 0); + if (fd < 0) + { + rt_kprintf("[rsoc26] cannot open %s; format/mount filesystem first\n", RSOC26_DATA_FILE); + return -RT_ERROR; + } + if (write(fd, line, length) != length) + { + close(fd); + rt_kprintf("[rsoc26] write Data.txt failed\n"); + return -RT_ERROR; + } + close(fd); + return RT_EOK; +} + +static void rsoc26_sample(void) +{ + char payload[96]; + float temp; + float humi; + + if (rsoc26_sensor_init() != RT_EOK) + return; + + temp = aht10_read_temperature(aht_dev); + humi = aht10_read_humidity(aht_dev); + sample_count++; + + rt_snprintf(payload, sizeof(payload), + "{\"temp\":%d.%d,\"humi\":%d.%d,\"count\":%d}", + (int)temp, (int)(temp * 10) % 10, + (int)humi, (int)(humi * 10) % 10, sample_count); + + rt_kprintf("[rsoc26] Temp: %d.%d C; Humi: %d.%d %% ; Count: %d\n", + (int)temp, (int)(temp * 10) % 10, + (int)humi, (int)(humi * 10) % 10, sample_count); + rsoc26_append_data(temp, humi); + + if (mqtt_started) + paho_mqtt_publish(&mqtt_client, QOS1, RSOC26_PUB_TOPIC, payload); +} +MSH_CMD_EXPORT(rsoc26_sample, read AHT21 append Data.txt and publish MQTT); + +static void rsoc26_mqtt_start(int argc, char **argv) +{ + MQTTPacket_connectData condata = MQTTPacket_connectData_initializer; + static char client_id[32]; + static char mqtt_username[32]; + static char mqtt_password[64]; + + if (argc != 3) + { + rt_kprintf("usage: rsoc26_mqtt_start \n"); + return; + } + if (mqtt_started) + { + rt_kprintf("[rsoc26] MQTT client already started\n"); + return; + } + + rt_memset(&mqtt_client, 0, sizeof(mqtt_client)); + mqtt_client.uri = RSOC26_MQTT_URI; + /* EMQX authorization is configured for the fixed client ID "root1". */ + rt_strncpy(client_id, "root1", sizeof(client_id)); + /* + * MQTT runs in a background thread. Copy shell arguments first, because + * the command-line buffer is released after this function returns. + */ + rt_strncpy(mqtt_username, argv[1], sizeof(mqtt_username)); + rt_strncpy(mqtt_password, argv[2], sizeof(mqtt_password)); + rt_memcpy(&mqtt_client.condata, &condata, sizeof(condata)); + mqtt_client.condata.clientID.cstring = client_id; + mqtt_client.condata.username.cstring = mqtt_username; + mqtt_client.condata.password.cstring = mqtt_password; + mqtt_client.condata.keepAliveInterval = 30; + mqtt_client.condata.cleansession = 1; + mqtt_client.buf_size = mqtt_client.readbuf_size = 1024; + mqtt_client.buf = rt_calloc(1, mqtt_client.buf_size); + mqtt_client.readbuf = rt_calloc(1, mqtt_client.readbuf_size); + if (!mqtt_client.buf || !mqtt_client.readbuf) + { + rt_kprintf("[rsoc26] MQTT buffer allocation failed\n"); + return; + } + /* Install the subscription table before the worker connects. Paho then + * subscribes after CONNACK and also restores it after a reconnection. */ + mqtt_client.messageHandlers[0].topicFilter = rt_strdup(RSOC26_CMD_TOPIC); + mqtt_client.messageHandlers[0].callback = rsoc26_message_callback; + mqtt_client.messageHandlers[0].qos = QOS1; + mqtt_client.defaultMessageHandler = rsoc26_message_callback; + paho_mqtt_start(&mqtt_client); + mqtt_started = RT_TRUE; + rt_kprintf("[rsoc26] MQTT started; command topic: %s (send: toggle)\n", RSOC26_CMD_TOPIC); +} +MSH_CMD_EXPORT_ALIAS(rsoc26_mqtt_start, mqtt_start, start MQTT: mqtt_start user password); + +static void rsoc26_cloud_toggle(void) +{ + if (!mqtt_client.isconnected) + { + rt_kprintf("[rsoc26] MQTT is not connected\n"); + return; + } + + /* Publish through EMQX; the subscription callback performs the LED action. */ + paho_mqtt_publish(&mqtt_client, QOS1, RSOC26_CMD_TOPIC, "toggle"); + rt_kprintf("[rsoc26] cloud toggle command published\n"); +} +MSH_CMD_EXPORT_ALIAS(rsoc26_cloud_toggle, cloud_toggle, publish toggle through EMQX); + +static void rsoc26_led(void) +{ + rsoc26_led_toggle(); + rt_kprintf("[rsoc26] PF12 red LED toggled\n"); +} +MSH_CMD_EXPORT(rsoc26_led, toggle Spark-1 PF12 red LED); + +static void rsoc26_wifi_init(void) +{ + int result = wifi_spi_device_init(); + rt_kprintf("[rsoc26] RW007 init result: %d\n", result); +} +MSH_CMD_EXPORT(rsoc26_wifi_init, initialize RW007 WiFi device); + +static void rsoc26_wifi_auto_connect(void *parameter) +{ + RT_UNUSED(parameter); + + /* The board initializes RW007 itself; wait for its WLAN device to appear. */ + rt_thread_mdelay(5000); + + if (rt_wlan_connect(RSOC26_WIFI_SSID, RSOC26_WIFI_PASSWORD) != RT_EOK) + rt_kprintf("[rsoc26] Wi-Fi connect request failed\n"); + else + rt_kprintf("[rsoc26] Wi-Fi connecting: %s\n", RSOC26_WIFI_SSID); +} + +static void rsoc26_mount_font(void) +{ + extern struct rt_device *fal_blk_device_create(const char *partition_name); + struct rt_device *font_dev = fal_blk_device_create("font"); + + if (font_dev == RT_NULL) + { + rt_kprintf("[rsoc26] create font block device failed\n"); + return; + } + if (dfs_mount(font_dev->parent.name, "/font", "elm", 0, 0) == RT_EOK) + rt_kprintf("[rsoc26] font partition mounted at /font\n"); + else + rt_kprintf("[rsoc26] mount font failed (font data is not necessarily a FAT filesystem)\n"); +} +MSH_CMD_EXPORT(rsoc26_mount_font, create and mount FAL font partition at /font); + +static int rsoc26_iot_init(void) +{ + rt_thread_t tid; + + rt_pin_mode(RSOC26_LED_PIN, PIN_MODE_OUTPUT); + rt_pin_write(RSOC26_LED_PIN, PIN_LOW); + + tid = rt_thread_create("rsocwifi", rsoc26_wifi_auto_connect, RT_NULL, + 2048, 20, 10); + if (tid != RT_NULL) + rt_thread_startup(tid); + else + rt_kprintf("[rsoc26] create Wi-Fi startup thread failed\n"); + return RT_EOK; +} +INIT_APP_EXPORT(rsoc26_iot_init);