diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day2/README.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day2/README.md" new file mode 100644 index 0000000000000000000000000000000000000000..e8b73bfc9212a69f7a3bc3840184f3f2d61b0fd4 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day2/README.md" @@ -0,0 +1,29 @@ +# 第4组 + +## Part 2 多线程实验 + +### 1. 实现 + +| 线程 | 方式 | 优先级/时间片/栈 | 功能 | +|---|---|---|---| +| th_led1 | 动态 create | 16/5/1024 | 翻转 LED_R,打印 count+tick | +| th_led2 | 静态 init | 16/5/全局栈+TCB | 翻转 LED_B,打印 count | +| th_monitor | 动态 create | 10/5/1024 | 每2s打印状态+剩余栈;按键挂起/恢复 th_led1 | + +`INIT_APP_EXPORT` 自动加载;`MSH_CMD_EXPORT(thread_dump)` 导出命令。 + +### 2. 运行结果 + +``` +[th_led1] count=177 tick=88569 +[th_led2] count=177 +[th_led1] count=178 tick=89073 +[th_led2] count=178 +[th_monitor] tick=90567 + th_led1 : stat=suspend prio=16 剩余栈=780/1024 + th_led2 : stat=suspend prio=16 剩余栈=776/1024 +``` + +- 两工作线程计数同步交替,时间片轮转正常 +- monitor 每 2s 准点抢占打印,优先级正常 +- 按键可挂起/恢复 th_led1 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day2/thread_study.c" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day2/thread_study.c" new file mode 100644 index 0000000000000000000000000000000000000000..9da2a3ca11914f3790cabe3ac368cc074a708fe0 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day2/thread_study.c" @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-22 wangqi the first version + * + * 作业说明: + * - 动态创建 th_led1 (优先级16, 时间片5, 栈1024) 翻转 LED_R + * - 静态创建 th_led2 (优先级16, 时间片5, 全局栈+TCB) 翻转 LED_B + * - 监控线程 th_monitor (优先级10) 每2s打印状态 + 轮询按键控制挂起/恢复 + * - 使用 INIT_APP_EXPORT 自动初始化,MSH_CMD_EXPORT 导出自定义命令 + */ + +#include +#include +#include + +#define THREAD_PRIORITY_LED 16 +#define THREAD_PRIORITY_MON 10 +#define THREAD_TIMESLICE 5 +#define THREAD_STACK_SIZE_LED1 1024 + +// 引脚定义(请按原理图修改) +#ifndef LED_R_PIN +#define LED_R_PIN GET_PIN(F, 12) +#endif +#ifndef LED_B_PIN +#define LED_B_PIN GET_PIN(F, 11) +#endif +#ifndef KEY0_PIN +#define KEY0_PIN GET_PIN(C, 1) +#endif + +static struct rt_thread led2_thread; +static rt_uint8_t led2_stack[512]; + +static rt_thread_t led1_thread = RT_NULL; +static rt_thread_t monitor_thread = RT_NULL; + +// 新增:用于通知 th_led1 挂起的信号量 +static rt_sem_t led1_sem = RT_NULL; + +static volatile rt_uint8_t key_press_count = 0; // 用于切换挂起/恢复 + +/* ---------- 工具函数 ---------- */ +static rt_uint32_t my_get_stack_remain(rt_thread_t thread) +{ + rt_uint8_t *ptr; + rt_uint32_t remain = 0; + + if (thread == RT_NULL) return 0; + + ptr = (rt_uint8_t *)thread->stack_addr; + while (ptr < (rt_uint8_t *)(thread->stack_addr + thread->stack_size)) + { + if (*ptr != '#') break; + ptr++; + remain++; + } + return remain; +} + +static const char* get_thread_stat_str(rt_uint8_t stat) +{ + switch (stat) + { + case RT_THREAD_INIT: return "init"; + case RT_THREAD_READY: return "ready"; + case RT_THREAD_RUNNING: return "running"; + case RT_THREAD_SUSPEND: return "suspend"; + case RT_THREAD_CLOSE: return "close"; + default: return "unknown"; + } +} + +/* ---------- 工作线程1 ---------- */ +static void th_led1_entry(void *parameter) +{ + rt_uint32_t count = 0; + while (1) + { + // 修改:使用信号量等待代替固定的 mdelay + // 等待 500 个系统节拍,如果超时则继续执行,如果收到信号则立即返回 + rt_err_t result = rt_sem_take(led1_sem, 500); + + if (result == -RT_ETIMEOUT) + { + // 正常超时,执行 LED 翻转 + rt_pin_write(LED_R_PIN, !rt_pin_read(LED_R_PIN)); + rt_kprintf("[th_led1] count=%d tick=%d\n", count, rt_tick_get()); + count++; + } + else if (result == RT_EOK) + { + // 收到了来自 th_monitor 的信号,主动挂起自己 + rt_kprintf("[th_led1] Received signal, suspending self.\n"); + rt_thread_suspend(rt_thread_self()); + // 线程被恢复后,会从 suspend 处继续执行,即回到 while(1) 循环开头 + } + } +} + +/* ---------- 工作线程2 ---------- */ +static void th_led2_entry(void *parameter) +{ + rt_uint32_t count = 0; + while (1) + { + rt_pin_write(LED_B_PIN, !rt_pin_read(LED_B_PIN)); + rt_kprintf("[th_led2] count=%d\n", count); + count++; + rt_thread_mdelay(500); + } +} + +/* ---------- 监控线程(轮询按键) ---------- */ +static void th_monitor_entry(void *parameter) +{ + rt_uint8_t key_prev = 1; // 假设按键上拉,未按下为高电平 + rt_uint8_t key_cur; + + while (1) + { + // ----- 轮询按键(检测下降沿) ----- + key_cur = rt_pin_read(KEY0_PIN); + if ((key_prev == 1) && (key_cur == 0)) // 下降沿 + { + rt_thread_mdelay(20); // 消抖 + if (rt_pin_read(KEY0_PIN) == 0) + { + key_press_count++; + rt_kprintf("[th_monitor] Key Pressed %d times.\n", key_press_count); + + // 修改:通过信号量通知 th_led1,而不是直接挂起它 + if (led1_thread != RT_NULL) + { + if (key_press_count % 2 == 1) // 奇数次按键,请求挂起 + { + rt_sem_release(led1_sem); + rt_kprintf("[th_monitor] Signal sent to suspend th_led1.\n"); + } + else // 偶数次按键,恢复 + { + rt_thread_resume(led1_thread); + rt_kprintf("[th_monitor] th_led1 Resumed.\n"); + } + } + else + { + rt_kprintf("[th_monitor] Warning: led1_thread is NULL.\n"); + } + } + } + key_prev = key_cur; + + // ----- 每2秒打印一次状态 ----- + static rt_tick_t last_print = 0; + if (rt_tick_get() - last_print >= RT_TICK_PER_SECOND * 2) + { + last_print = rt_tick_get(); + rt_kprintf("[th_monitor] tick=%d\n" + " th_led1 : stat=%s pri=%d stack=%d/%d\n" + " th_led2 : stat=%s pri=%d stack=%d/%d\n", + rt_tick_get(), + (led1_thread ? get_thread_stat_str(led1_thread->stat) : "null"), + (led1_thread ? led1_thread->current_priority : 0), + (led1_thread ? my_get_stack_remain(led1_thread) : 0), + (led1_thread ? led1_thread->stack_size : 0), + get_thread_stat_str(led2_thread.stat), + led2_thread.current_priority, + my_get_stack_remain(&led2_thread), + led2_thread.stack_size); + } + rt_thread_mdelay(10); // 每10ms轮询一次 + } +} + +/* ---------- 初始化 ---------- */ +static int thread_study_init(void) +{ + rt_err_t result; + + rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); + rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); + rt_pin_mode(KEY0_PIN, PIN_MODE_INPUT_PULLUP); + + // 新增:创建信号量 + led1_sem = rt_sem_create("led1sem", 0, RT_IPC_FLAG_FIFO); + if (led1_sem == RT_NULL) + { + rt_kprintf("Failed to create semaphore!\n"); + return -1; + } + + // 1. 动态创建 th_led1 + led1_thread = rt_thread_create("th_led1", + th_led1_entry, + RT_NULL, + THREAD_STACK_SIZE_LED1, + THREAD_PRIORITY_LED, + THREAD_TIMESLICE); + if (led1_thread != RT_NULL) + { + result = rt_thread_startup(led1_thread); + if (result != RT_EOK) rt_kprintf("Failed to startup th_led1!\n"); + } + else rt_kprintf("Failed to create th_led1!\n"); + + // 2. 静态初始化 th_led2 + result = rt_thread_init(&led2_thread, + "th_led2", + th_led2_entry, + RT_NULL, + &led2_stack[0], + sizeof(led2_stack), + THREAD_PRIORITY_LED, + THREAD_TIMESLICE); + if (result == RT_EOK) + { + result = rt_thread_startup(&led2_thread); + if (result != RT_EOK) rt_kprintf("Failed to startup th_led2!\n"); + } + else rt_kprintf("Failed to init th_led2!\n"); + + // 3. 动态创建 th_monitor + monitor_thread = rt_thread_create("th_monitor", + th_monitor_entry, + RT_NULL, + 1024, + THREAD_PRIORITY_MON, + 10); + if (monitor_thread != RT_NULL) + { + result = rt_thread_startup(monitor_thread); + if (result != RT_EOK) rt_kprintf("Failed to startup th_monitor!\n"); + } + else rt_kprintf("Failed to create th_monitor!\n"); + + return 0; +} +INIT_APP_EXPORT(thread_study_init); + +/* ---------- MSH 命令 ---------- */ +void thread_dump(void) +{ + rt_kprintf("\n--- Thread Status Dump ---\n" + " th_led1 : stat=%s pri=%d stack=%d/%d\n" + " th_led2 : stat=%s pri=%d stack=%d/%d\n" + "--------------------------\n", + (led1_thread ? get_thread_stat_str(led1_thread->stat) : "null"), + (led1_thread ? led1_thread->current_priority : 0), + (led1_thread ? my_get_stack_remain(led1_thread) : 0), + (led1_thread ? led1_thread->stack_size : 0), + get_thread_stat_str(led2_thread.stat), + led2_thread.current_priority, + my_get_stack_remain(&led2_thread), + led2_thread.stack_size); +} +MSH_CMD_EXPORT(thread_dump, Dump thread status for homework); diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/AHT10_example.c" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/AHT10_example.c" new file mode 100644 index 0000000000000000000000000000000000000000..9bac2f544be64632b0d65e5e8ed07cd3b74e041e --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/AHT10_example.c" @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-26 wangqi the first version + */ +#include +#include +#define DBG_TAG "aht10" +#define DBG_LVL DBG_LOG +#include + +int aht10_read_entry(void *par) +{ + aht10_device_t aht10 = aht10_init("i2c3"); + if (aht10 == RT_NULL) + { + LOG_E("aht10 init fail"); + return -RT_ERROR; + } + while(1) + { + float temp=aht10_read_temperature(aht10); + float humi=aht10_read_humidity(aht10); + rt_kprintf("temp = %.2f humi = %.2f\n",temp,humi); + rt_thread_mdelay(500); + } +} + +int aht10_thread_init(void) +{ + rt_thread_t AHT10_thread = rt_thread_create("aht10", aht10_read_entry, RT_NULL, 10244, 10, 10); + if (AHT10_thread == RT_NULL) + { + LOG_E("AHT10_thread create fail"); + return -RT_ERROR; + } + rt_thread_startup(AHT10_thread); +} + +MSH_CMD_EXPORT(aht10_thread_init,aht10_thread_init); diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234421850.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234421850.png" new file mode 100644 index 0000000000000000000000000000000000000000..99e3560fe3eb0bd5282cfbe9bb462573db37aa55 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234421850.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234447455.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234447455.png" new file mode 100644 index 0000000000000000000000000000000000000000..e8fd47ce3747d6e29a62d74270bd0d767e844857 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234447455.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234459795.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234459795.png" new file mode 100644 index 0000000000000000000000000000000000000000..82296b6db7ad0c006bbc98adb695659b6eba2df8 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234459795.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234524021.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234524021.png" new file mode 100644 index 0000000000000000000000000000000000000000..702db4c5cea22e1ae992a86f0fdf6ad5773782aa Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234524021.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234535724.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234535724.png" new file mode 100644 index 0000000000000000000000000000000000000000..739aa6f29a11433db1b5757d95f4dd7b8f74ee9c Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234535724.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234544138.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234544138.png" new file mode 100644 index 0000000000000000000000000000000000000000..18b1c0c4f0e73413baa46f3e1c4a1a38f12f9956 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234544138.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234554702.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234554702.png" new file mode 100644 index 0000000000000000000000000000000000000000..8ba0384f70fae98f343dfaaf6a1ad4799722c936 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234554702.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234628203.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234628203.png" new file mode 100644 index 0000000000000000000000000000000000000000..5232d230ce1e4350216634cfe4fa243d3ad3580e Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234628203.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234637995.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234637995.png" new file mode 100644 index 0000000000000000000000000000000000000000..e58dbad789a12bdef8f7ea8790296f0a8f48d820 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234637995.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234645585.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234645585.png" new file mode 100644 index 0000000000000000000000000000000000000000..d9661f23db4501663e7015e3790e4a2ea6dfa607 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234645585.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234922201.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234922201.png" new file mode 100644 index 0000000000000000000000000000000000000000..05c8d389d21957c51604a28cf338f2d9a6c11d3b Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826234922201.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826235029389.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826235029389.png" new file mode 100644 index 0000000000000000000000000000000000000000..a4ef042d6900ecc00470bf8c8ecbda8c5105addd Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826235029389.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826235116317.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826235116317.png" new file mode 100644 index 0000000000000000000000000000000000000000..16088d03610f2ea8a1a8fe4278eb486414eacc2c Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260826235116317.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827133156278.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827133156278.png" new file mode 100644 index 0000000000000000000000000000000000000000..1ae3dde9d84653fab30c4b8bba1c2adb7afadbb7 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827133156278.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827154412135.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827154412135.png" new file mode 100644 index 0000000000000000000000000000000000000000..c779d66ec3f9786f403b32cb9e3ee56d39128148 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827154412135.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827173526248.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827173526248.png" new file mode 100644 index 0000000000000000000000000000000000000000..71e2a8b98d21049a68aa05d426dd1d5ec2270292 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827173526248.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827173544673.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827173544673.png" new file mode 100644 index 0000000000000000000000000000000000000000..cfddd39d0094846615c489329ec09901c3423ae1 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/FIGURES/image-20260827173544673.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/README.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/README.md" new file mode 100644 index 0000000000000000000000000000000000000000..04f62a7ea65079b2177d614d1beff4262489d197 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/README.md" @@ -0,0 +1,274 @@ +# 任务1.使用AHT10软件包采集温湿度并上传到emqx(如果你有其他MQTT服务器也可上传到对应服务器上); + +## 1. 读取AHT10 传感器的温湿度数据(`AHT10_example.c`) + +![](FIGURES/image-20260826234447455.png) + +![image-20260826234459795](FIGURES/image-20260826234459795.png) + +## 2.将 `AHT10_example.c` 中的传感器采集功能与 `test.c` 中的 MQTT 客户端功能结合起来。(`test_1.c`) + +![image-20260826234544138](FIGURES/image-20260826234544138.png) + +**![image-20260826234524021](FIGURES/image-20260826234524021.png)** + +![image-20260826234535724](FIGURES/image-20260826234535724.png) + +![image-20260826234421850](FIGURES/image-20260826234421850.png) + +![image-20260826234628203](FIGURES/image-20260826234628203.png) + + + +> 需要先连接好WiFi,通过msh输入ka_mqtt_1命令。按顺序执行下列命令: +> +> ``` +> msh />wifi scan +> ``` +> +> ``` +> msh />wifi join +> ``` +> +> ``` +> msh />pin +> ``` +> +> ``` +> msh />ka_mqtt_1 +> ``` + +![image-20260827133156278](FIGURES/image-20260827133156278.png) + +``` +msh />ka_mqtt_1 +msh />aht10 init success + +[I] >> The ID of the Kawaii client is: rtthread999 +[I] >> ../packages/kawaii-mqtt-latest/mqttclient/mqttclient.c:991 mqtt_connect_with_results()... mqtt connect success... +[I] >> ----------------------------------------------------------------------------------- +[I] >> ../packages/kawaii-mqtt-latest/test/test_1.c:54 sub_topic_handle1()... +topic: RTT-publish +message:{ + "msg": "hello" +} +[I] >> ----------------------------------------------------------------------------------- +``` +--- + + +# 任务2.梳理文件系统启动流程,熟悉shell命令使用。能够将font分区给挂载上。在温湿度上传上传云端的同时,将数据同步放在文件系统处,文件名为:Data.txt; +文件内容: Temp:XX ; Humi:XX ; Count: 1(自上电起所采集的数据次数) + Temp:XX ; Humi:XX ; Count: 2(自上电起所采集的数据次数) + Temp:XX ; Humi:XX ; Count: 3(自上电起所采集的数据次数) + +## 1 .**梳理 RT-Thread 文件系统启动流程** + +RT-Thread 的虚拟文件系统(DFS)启动和挂载遵循严格的自底向上流程: + +1. **DFS 框架初始化**:`dfs_init()` 初始化虚拟文件系统的基础数据结构和接口。 + +2. **文件系统类型注册**:初始化具体的文件系统(如 elm-FatFS),并通过 `dfs_register()` 注册到 DFS 中。 + +3. **底层存储介质探测与注册** + - 初始化硬件接口(如 SPI)。 + - 调用 `rt_sfud_flash_probe()` 探测并注册 SPI Flash 设备(如 `w25qxx`)。 + +4. **Flash 抽象层(FAL)初始化**:解析分区表(`fal_cfg.h`),将 SPI Flash 划分出不同的分区(如 `filesystem` 分区)。 + +5. **创建 MTD 设备并挂载**:将 FAL 抽象出的分区(如 `filesystem`)创建为 MTD 块设备(如 `filesystem`),最后调用 `dfs_mount()` 将其挂载到指定的目录节点(如 `/data`)。 + + ![image-20260826234637995](FIGURES/image-20260826234637995.png) + + ![image-20260826234645585](FIGURES/image-20260826234645585.png) + + ![image-20260826235029389](FIGURES/image-20260826235029389.png) + + > 将RTC添加到组件。 + +![image-20260826235116317](FIGURES/image-20260826235116317.png) +## 2 .**熟悉 Shell (MSH) 命令使用** + +挂载成功后,你可以直接在 FinSH 控制台输入 MSH 命令来操作文件: + +- `list_device`:查看系统中已注册的设备及引用计数(挂载成功后,flash 设备的 ref count 会 +1)。 +- `mkfs -t elm filesystem`:**首次使用或格式化失败时**,用于格式化指定的块设备(如 `filesystem`)。 +- `ls /data`:查看挂载目录下的文件。 +- `echo "test" /data/Data.txt`:向文件中写入字符串。 +- `cat /data/Data.txt`:读取并显示文件内容。 +- `rm /data/Data.txt`:删除文件。 + +## 3.代码实现方案(`test_2.c`) +在`test_1.c`基础上修改 MQTT 发布函数(`mqtt_publish_handle1`),并引入文件操作。在读取传感器数据后,先将其格式化并写入文件,然后再发布到 MQTT 服务器。 + +### 3.1 + +```c +msh />ls +Directory /: +fal +``` + +```c +fp = fopen("/fal/Data.txt", "a"); // 路径 "/fal/Data.txt" 是根据你之前的日志推断出的正确路径。 +``` + +### 3.2 `mqtt_publish_handle1`**函数** + +- 在读取数据后,`data_count` 自增。 +- 使用 `rt_snprintf` 按照 `Temp:XX ; Humi:XX ; Count: X` 的格式生成日志字符串。 +- 使用 `fopen("/fal/Data.txt", "a")` 以**追加模式**打开文件。这是关键,确保每次写入都在文件末尾,而不是覆盖原有内容。 +- 使用 `fputs` 写入数据,并用 `fflush` 和 `fclose` 确保数据落盘。 + +### 3.3重新编译并下载程序 + +运行 `ka_mqtt_2` 命令后,设备会每隔 4 秒采集一次数据,上传到云端,并同时将一条格式化的记录追加到 `/fal/Data.txt` 文件中。 + +![image-20260826234922201](FIGURES/image-20260826234922201.png) + +运行 `ka_mqtt_2` 命令后,设备会每隔 4 秒采集一次数据,上传到云端,并同时将一条格式化的记录追加到 `/fal/Data.txt` 文件中。 + +你可以通过在 FinSH 控制台输入 `cat /fal/Data.txt` 来查看文件内容,验证数据是否被正确记录。 + +``` +msh />ka_mqtt_2 +msh />aht10 init success + +[I] >> The ID of the Kawaii client is: rtthread999 +[I] >> ../packages/kawaii-mqtt-latest/mqttclient/mqttclient.c:991 mqtt_connect_with_results()... mqtt connect success...[152437] W/time: Cannot find a RTC device! +[FILE LOG] Temp:25.91 ; Humi:50.19 ; Count: 1 +[156618] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.15 ; Humi:54.23 ; Count: 2 +[160807] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.17 ; Humi:54.51 ; Count: 3 +[164990] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.18 ; Humi:54.58 ; Count: 4 +[169173] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.21 ; Humi:54.84 ; Count: 5 +[173361] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.21 ; Humi:54.87 ; Count: 6 +[177542] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.24 ; Humi:54.81 ; Count: 7 +[181727] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.26 ; Humi:54.88 ; Count: 8 +[185910] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.29 ; Humi:54.91 ; Count: 9 +[190096] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.29 ; Humi:55.09 ; Count: 10 +[194285] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.31 ; Humi:55.01 ; Count: 11 +[198468] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.32 ; Humi:55.38 ; Count: 12 +[202647] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.34 ; Humi:55.42 ; Count: 13 +[206833] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.39 ; Humi:55.66 ; Count: 14 +[211018] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.38 ; Humi:55.52 ; Count: 15 +[215202] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.40 ; Humi:55.51 ; Count: 16 +[219385] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.42 ; Humi:55.57 ; Count: 17 + +[I] >> ----------------------------------------------------------------------------------- +[I] >> ../packages/kawaii-mqtt-latest/test/test_2.c:67 sub_topic_handle1()... +topic: RTT-publish +message:{ + "msg": "hello" +} +[I] >> -----------------------------------------------------------------------------------[223569] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.44 ; Humi:55.54 ; Count: 18 +[227754] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.44 ; Humi:55.46 ; Count: 19 +[231937] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.48 ; Humi:55.45 ; Count: 20 +[236120] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.49 ; Humi:55.49 ; Count: 21 +[240305] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.51 ; Humi:55.48 ; Count: 22 +[244484] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.51 ; Humi:55.44 ; Count: 23 +[248670] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.54 ; Humi:55.42 ; Count: 24 +[252849] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.55 ; Humi:55.54 ; Count: 25 +[257035] W/time: Cannot find a RTC device! +[FILE LOG] Temp:26.56 ; Humi:55.61 ; Count: 26 + +msh />cat /fal/Data.txt +Temp:-50.00 ; Humi:0.00 ; Count: 1 +Temp:25.99 ; Humi:50.74 ; Count: 2 +Temp:26.00 ; Humi:50.68 ; Count: 3 +Temp:25.99 ; Humi:50.60 ; Count: 4 +Temp:25.96 ; Humi:50.58 ; Count: 5 +Temp:25.98 ; Humi:50.54 ; Count: 6 +Temp:25.97 ; Humi:50.41 ; Count: 7 +Temp:25.96 ; Humi:50.53 ; Count: 8 +Temp:25.99 ; Humi:50.53 ; Count: 9 +Temp:25.99 ; Humi:50.48 ; Count: 10 +Temp:25.95 ; Humi:50.25 ; Count: 11 +``` + +![image-20260827154412135](FIGURES/image-20260827154412135.png) + +--- + +# 任务 3.利用云端给开发板发送指令然后实现小灯翻转(`test_3.c`) + +> 在 MQTT 的订阅回调函数中解析云端指令,并调用 RT-Thread 的 GPIO 接口来控制 LED。 +> +> 红色 LED 灯连接在 `PIN_LED_R` (PF12) 引脚上。我们将利用已有的 MQTT 订阅功能来实现远程控制。 + +## 1.**实现步骤** + +- **修改订阅回调函数**:在收到 MQTT 消息的 `sub_topic_handle1` 函数中,判断消息内容。 +- **添加 LED 控制逻辑**:如果消息是特定指令(例如 "LED_TOGGLE"),则读取 LED 当前状态并翻转它。 +- #### **安装** `cJSON` **软件包** + +## 2.**测试** + +1. **编译并下载**:将修改后的 `test_3.c` 编译并下载到开发板。 + +2. **运行程序**:在 FinSH 控制台输入 `ka_mqtt_3` 启动 MQTT 客户端。设备会自动订阅 `RTT-publish` 主题。 + +3. **发送指令:** + - 打开你的 MQTT 客户端工具(如 MQTTX, MQTT.fx 等)。 + - 连接到同一个 MQTT 服务器 (`broker.emqx.io`)。 + - 向主题 `RTT-publish` 发布消息,消息内容(Payload)填写 `LED_TOGGLE`。 + +4. **观察现象:** + - 开发板上的红色 LED 灯(PF12)状态会发生翻转(亮变灭,或灭变亮)。 + + - 在 FinSH 控制台可以看到类似 `[CMD] LED ON` 的打印信息。 + + +``` +[CMD] 收到 LED_TOGGLE 指令,正在翻转LED... +[CMD] LED ON + +[I] >> ----------------------------------------------------------------------------------- +[I] >> ../packages/kawaii-mqtt-latest/test/test_3.c:66 sub_topic_handle1()... +topic: RTT-publish +message:{ + "msg": "LED_TOGGLE" +} +[I] >> ----------------------------------------------------------------------------------- +``` + + +![image-20260827173526248](FIGURES/image-20260827173526248.png) + +``` +[CMD] 收到 LED_TOGGLE 指令,正在翻转LED... +[CMD] LED OFF + +[I] >> ----------------------------------------------------------------------------------- +[I] >> ../packages/kawaii-mqtt-latest/test/test_3.c:66 sub_topic_handle1()... +topic: RTT-publish +message:{ + "msg": "LED_TOGGLE" +} +[I] >> ----------------------------------------------------------------------------------- +``` + +![image-20260827173544673](FIGURES/image-20260827173544673.png) diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_1.c" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_1.c" new file mode 100644 index 0000000000000000000000000000000000000000..6c553a84d6056e92b80253a20b403c474e448601 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_1.c" @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-27 wangqi the first version + */ +//使用AHT10软件包采集温湿度并上传到emqx(如果你有其他MQTT服务器也可上传到对应服务器上); +#include +#include +#include +#include +#include +#include "mqttclient.h" +#include // 1. 引入 AHT10 头文件 + +#ifndef KAWAII_MQTT_HOST +#define KAWAII_MQTT_HOST "broker.emqx.io" // 修改为 EMQX 公共服务器 +#endif + +#ifndef KAWAII_MQTT_PORT +#define KAWAII_MQTT_PORT "1883" +#endif + +#ifndef KAWAII_MQTT_CLIENTID +#define KAWAII_MQTT_CLIENTID "rtthread-aht10" // 修改客户端ID,避免冲突 +#endif + +#ifndef KAWAII_MQTT_USERNAME +#define KAWAII_MQTT_USERNAME "" // EMQX 公共服务器无需用户名 +#endif + +#ifndef KAWAII_MQTT_PASSWORD +#define KAWAII_MQTT_PASSWORD "" // EMQX 公共服务器无需密码 +#endif + +#ifndef KAWAII_MQTT_SUBTOPIC +#define KAWAII_MQTT_SUBTOPIC "rtt-sub" +#endif + +#ifndef KAWAII_MQTT_PUBTOPIC +#define KAWAII_MQTT_PUBTOPIC "sensor/aht10/data" // 修改发布主题 +#endif + +// 定义一个全局变量来保存 AHT10 设备句柄 +static aht10_device_t aht10_dev = RT_NULL; + +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("-----------------------------------------------------------------------------------"); +} + +// 2. 修改发布处理函数,读取传感器数据并发布 +static int mqtt_publish_handle1(mqtt_client_t *client) +{ + if (aht10_dev == RT_NULL) { + return -RT_ERROR; + } + + mqtt_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.qos = QOS0; + + // 读取温湿度数据 + float temp = aht10_read_temperature(aht10_dev); + float humi = aht10_read_humidity(aht10_dev); + + // 将数据格式化为 JSON 字符串 + char json_payload[64]; + rt_snprintf(json_payload, sizeof(json_payload), "{\"temperature\": %.2f, \"humidity\": %.2f}", temp, humi); + + msg.payload = (void *)json_payload; + + // 发布消息 + return mqtt_publish(client, KAWAII_MQTT_PUBTOPIC, &msg); +} + +static void kawaii_mqtt_demo(void *parameter) +{ + mqtt_client_t *client = NULL; + + // 延迟等待网络就绪 + rt_thread_delay(6000); + + // 3. 初始化 AHT10 传感器 + aht10_dev = aht10_init("i2c3"); // 请根据你的板子修改 I2C 总线名称,如 "i2c1" + if (aht10_dev == RT_NULL) { + rt_kprintf("aht10 init fail\n"); + return; + } + rt_kprintf("aht10 init success\n"); + + 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); // 每 4 秒发布一次数据 + } +} + +int ka_mqtt_1(void) +{ + rt_thread_t tid_mqtt; + tid_mqtt = rt_thread_create("kawaii_demo", kawaii_mqtt_demo, RT_NULL, 2048, 17, 10); + if (tid_mqtt == RT_NULL) + { + return -RT_ERROR; + } + rt_thread_startup(tid_mqtt); + return RT_EOK; +} +MSH_CMD_EXPORT(ka_mqtt_1, Kawaii MQTT client test program); diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_2.c" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_2.c" new file mode 100644 index 0000000000000000000000000000000000000000..a208875c9fe5084321b1feecc308f52186c70a6b --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_2.c" @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-27 wangqi the first version + */ +//在温湿度上传上传云端的同时,将数据同步放在文件系统处,文件名为:Data.txt;文件内容: Temp:XX ; Humi:XX ; Count: 1(自上电起所采集的数据次数); +#include +#include +#include +#include +#include +#include "mqttclient.h" +#include // 1. 引入 AHT10 头文件 +#include // 用于字符串操作 (strlen) + +#ifndef KAWAII_MQTT_HOST +#define KAWAII_MQTT_HOST "broker.emqx.io" // 修改为 EMQX 公共服务器 +#endif + +#ifndef KAWAII_MQTT_PORT +#define KAWAII_MQTT_PORT "1883" +#endif + +#ifndef KAWAII_MQTT_CLIENTID +#define KAWAII_MQTT_CLIENTID "rtthread-aht10" // 修改客户端ID,避免冲突 +#endif + +#ifndef KAWAII_MQTT_USERNAME +#define KAWAII_MQTT_USERNAME "" // EMQX 公共服务器无需用户名 +#endif + +#ifndef KAWAII_MQTT_PASSWORD +#define KAWAII_MQTT_PASSWORD "" // EMQX 公共服务器无需密码 +#endif + +#ifndef KAWAII_MQTT_SUBTOPIC +#define KAWAII_MQTT_SUBTOPIC "rtt-sub" +#endif + +#ifndef KAWAII_MQTT_PUBTOPIC +#define KAWAII_MQTT_PUBTOPIC "sensor/aht10/data" // 修改发布主题 +#endif + +// 定义一个全局变量来保存 AHT10 设备句柄 +static aht10_device_t aht10_dev = RT_NULL; + +// 新增:用于记录数据采集次数的全局变量 +static int data_count = 0; + +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("-----------------------------------------------------------------------------------"); +} + +// 2. 修改发布处理函数,读取传感器数据并发布 +static int mqtt_publish_handle1(mqtt_client_t *client) +{ + if (aht10_dev == RT_NULL) { + return -RT_ERROR; + } + + // 读取温湿度数据 + float temp = aht10_read_temperature(aht10_dev); + float humi = aht10_read_humidity(aht10_dev); + + // 数据计数加一 + data_count++; + + // --- 任务二核心代码:将数据写入文件 --- + FILE *fp = NULL; + char log_buf[128] = {0}; + + // 1. 按照指定格式拼接字符串 + // 格式: Temp:XX ; Humi:XX ; Count: 1 + rt_snprintf(log_buf, sizeof(log_buf), + "Temp:%.2f ; Humi:%.2f ; Count: %d\n", + temp, humi, data_count); + + // 2. 以追加模式("a")打开文件。如果文件不存在则创建,存在则在末尾追加。 + // 路径 "/fal/Data.txt" 是根据你之前的日志推断出的正确路径。 + fp = fopen("/fal/Data.txt", "a"); + if (fp != NULL) + { + // 3. 将格式化好的字符串写入文件 + fputs(log_buf, fp); + + // 4. 强制刷新缓冲区,确保数据立即写入Flash,防止掉电丢失 + fflush(fp); + + // 5. 关闭文件 + fclose(fp); + + // 可选:在控制台打印,方便调试 + rt_kprintf("[FILE LOG] %s", log_buf); + } + else + { + rt_kprintf("[ERROR] Failed to open /fal/Data.txt for writing.\n"); + } + // --- 任务二代码结束 --- + + // --- 原有的 MQTT 发布逻辑 --- + mqtt_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.qos = QOS0; + + // 将数据格式化为 JSON 字符串用于 MQTT 发布 + char json_payload[64]; + rt_snprintf(json_payload, sizeof(json_payload), "{\"temperature\": %.2f, \"humidity\": %.2f}", temp, humi); + msg.payload = (void *)json_payload; + + // 发布消息 + return mqtt_publish(client, KAWAII_MQTT_PUBTOPIC, &msg); +} + +static void kawaii_mqtt_demo(void *parameter) +{ + mqtt_client_t *client = NULL; + + // 延迟等待网络就绪 + rt_thread_delay(6000); + + // 3. 初始化 AHT10 传感器 + aht10_dev = aht10_init("i2c3"); // 请根据你的板子修改 I2C 总线名称,如 "i2c1" + if (aht10_dev == RT_NULL) { + rt_kprintf("aht10 init fail\n"); + return; + } + rt_kprintf("aht10 init success\n"); + + 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); // 每 4 秒发布一次数据 + } +} + +int ka_mqtt_2(void) +{ + rt_thread_t tid_mqtt; + tid_mqtt = rt_thread_create("kawaii_demo", kawaii_mqtt_demo, RT_NULL, 2048, 17, 10); + if (tid_mqtt == RT_NULL) + { + return -RT_ERROR; + } + rt_thread_startup(tid_mqtt); + return RT_EOK; +} +MSH_CMD_EXPORT(ka_mqtt_2, Kawaii MQTT client test program); diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_3.c" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_3.c" new file mode 100644 index 0000000000000000000000000000000000000000..96202ff43d7c54943523a8909b2775e289c14fec --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/day5/test_3.c" @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-27 wangqi the first version + */ +//利用云端给开发板发送指令然后实现小灯翻转; +#include +#include +#include +#include +#include +#include "mqttclient.h" +#include // 1. 引入 AHT10 头文件 +#include // 用于字符串操作 (strlen) +#include "cJSON.h" // <--- 新增这一行 + +/* 配置 LED 灯引脚 */ +#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 "broker.emqx.io" // 修改为 EMQX 公共服务器 +#endif + +#ifndef KAWAII_MQTT_PORT +#define KAWAII_MQTT_PORT "1883" +#endif + +#ifndef KAWAII_MQTT_CLIENTID +#define KAWAII_MQTT_CLIENTID "rtthread-aht10" // 修改客户端ID,避免冲突 +#endif + +#ifndef KAWAII_MQTT_USERNAME +#define KAWAII_MQTT_USERNAME "" // EMQX 公共服务器无需用户名 +#endif + +#ifndef KAWAII_MQTT_PASSWORD +#define KAWAII_MQTT_PASSWORD "" // EMQX 公共服务器无需密码 +#endif + +#ifndef KAWAII_MQTT_SUBTOPIC +#define KAWAII_MQTT_SUBTOPIC "rtt-sub" +#endif + +#ifndef KAWAII_MQTT_PUBTOPIC +#define KAWAII_MQTT_PUBTOPIC "sensor/aht10/data" // 修改发布主题 +#endif + +// 定义一个全局变量来保存 AHT10 设备句柄 +static aht10_device_t aht10_dev = RT_NULL; + +// 新增:用于记录数据采集次数的全局变量 +static int data_count = 0; + +static void sub_topic_handle1(void* client, message_data_t* msg) +{ + (void) client; + + // 将接收到的消息 payload 转换为字符串 + char* payload = (char*)msg->message->payload; + KAWAII_MQTT_LOG_I("-----------------------------------------------------------------------------------"); + KAWAII_MQTT_LOG_I("%s:%d %s()...\ntopic: %s\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, payload); + KAWAII_MQTT_LOG_I("-----------------------------------------------------------------------------------"); + + // --- 任务三核心代码:解析JSON并控制LED --- + + // 1. 解析收到的 JSON 字符串 + cJSON *root = cJSON_Parse(payload); + if (root == NULL) + { + // 如果不是合法的 JSON 格式,打印错误并返回 + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + rt_kprintf("[JSON ERROR] Error before: %s\n", error_ptr); + } + return; + } + + // 2. 从 JSON 对象中获取 "msg" 字段的值 + cJSON *msg_item = cJSON_GetObjectItemCaseSensitive(root, "msg"); + if (cJSON_IsString(msg_item) && (msg_item->valuestring != NULL)) + { + // 3. 判断 "msg" 的值是否为 "LED_TOGGLE" + if (strcmp(msg_item->valuestring, "LED_TOGGLE") == 0) + { + rt_kprintf("[CMD] 收到 LED_TOGGLE 指令,正在翻转LED...\n"); // <--- 新增这行 + + // 确保引脚被设置为输出模式(每次回调都执行一次,确保安全) + rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); + + // 读取 LED 引脚的当前电平状态 + rt_base_t led_status = rt_pin_read(PIN_LED_R); + + // 翻转电平 + if (led_status == PIN_HIGH) + { + rt_pin_write(PIN_LED_R, PIN_LOW); // 点亮 LED + rt_kprintf("[CMD] LED ON\n"); + } + else + { + rt_pin_write(PIN_LED_R, PIN_HIGH); // 熄灭 LED + rt_kprintf("[CMD] LED OFF\n"); + } + } + } + + // 4. 释放 cJSON 对象占用的内存 + cJSON_Delete(root); +} + +// 2. 修改发布处理函数,读取传感器数据并发布 +static int mqtt_publish_handle1(mqtt_client_t *client) +{ + if (aht10_dev == RT_NULL) { + return -RT_ERROR; + } + + // 读取温湿度数据 + float temp = aht10_read_temperature(aht10_dev); + float humi = aht10_read_humidity(aht10_dev); + + // 数据计数加一 + data_count++; + + // --- 任务二核心代码:将数据写入文件 --- + FILE *fp = NULL; + char log_buf[128] = {0}; + + // 1. 按照指定格式拼接字符串 + // 格式: Temp:XX ; Humi:XX ; Count: 1 + rt_snprintf(log_buf, sizeof(log_buf), + "Temp:%.2f ; Humi:%.2f ; Count: %d\n", + temp, humi, data_count); + + // 2. 以追加模式("a")打开文件。如果文件不存在则创建,存在则在末尾追加。 + // 路径 "/fal/Data.txt" 是根据你之前的日志推断出的正确路径。 + fp = fopen("/fal/Data.txt", "a"); + if (fp != NULL) + { + // 3. 将格式化好的字符串写入文件 + fputs(log_buf, fp); + + // 4. 强制刷新缓冲区,确保数据立即写入Flash,防止掉电丢失 + fflush(fp); + + // 5. 关闭文件 + fclose(fp); + + // 可选:在控制台打印,方便调试 + rt_kprintf("[FILE LOG] %s", log_buf); + } + else + { + rt_kprintf("[ERROR] Failed to open /fal/Data.txt for writing.\n"); + } + // --- 任务二代码结束 --- + + // --- 原有的 MQTT 发布逻辑 --- + mqtt_message_t msg; + memset(&msg, 0, sizeof(msg)); + msg.qos = QOS0; + + // 将数据格式化为 JSON 字符串用于 MQTT 发布 + char json_payload[64]; + rt_snprintf(json_payload, sizeof(json_payload), "{\"temperature\": %.2f, \"humidity\": %.2f}", temp, humi); + msg.payload = (void *)json_payload; + + // 发布消息 + return mqtt_publish(client, KAWAII_MQTT_PUBTOPIC, &msg); +} + +static void kawaii_mqtt_demo(void *parameter) +{ + mqtt_client_t *client = NULL; + + // 延迟等待网络就绪 + rt_thread_delay(6000); + + // 3. 初始化 AHT10 传感器 + aht10_dev = aht10_init("i2c3"); // 请根据你的板子修改 I2C 总线名称,如 "i2c1" + if (aht10_dev == RT_NULL) { + rt_kprintf("aht10 init fail\n"); + return; + } + rt_kprintf("aht10 init success\n"); + + 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); // 每 4 秒发布一次数据 + } +} + +int ka_mqtt_3(void) +{ + rt_thread_t tid_mqtt; + tid_mqtt = rt_thread_create("kawaii_demo", kawaii_mqtt_demo, RT_NULL, 2048, 17, 10); + if (tid_mqtt == RT_NULL) + { + return -RT_ERROR; + } + rt_thread_startup(tid_mqtt); + return RT_EOK; +} +MSH_CMD_EXPORT(ka_mqtt_3, Kawaii MQTT client test program); + +// 一个简单的测试函数,用于验证 LED 引脚和电平逻辑 +void led_test(void) +{ + rt_kprintf("\n[TEST] 开始LED测试,请观察板载LED...\n"); + + // 1. 先确保引脚被设置为输出模式 + rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); + + // 2. 输出高电平 + rt_pin_write(PIN_LED_R, PIN_HIGH); + rt_kprintf("[TEST] 已设置 PIN_LED_R 为 HIGH (高电平)\n"); + rt_thread_mdelay(2000); // 等待2秒,观察现象 + + // 3. 输出低电平 + rt_pin_write(PIN_LED_R, PIN_LOW); + rt_kprintf("[TEST] 已设置 PIN_LED_R 为 LOW (低电平)\n"); + rt_thread_mdelay(2000); // 等待2秒,观察现象 + + rt_kprintf("[TEST] LED测试结束。\n"); +} +// 将测试函数导出到 FinSH 命令行,方便调用 +MSH_CMD_EXPORT(led_test, test led pin); diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/RT-Thread\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/RT-Thread\345\255\246\344\271\240\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..5d30ccdb2d4f8dedc189add7b9bb5f2bae6376e7 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/RT-Thread\345\255\246\344\271\240\347\254\224\350\256\260.md" @@ -0,0 +1,329 @@ +# RT-Thread 学习笔记 + +--- + +## 一、RT-Thread 内核启动流程与线程机制 + +针对嵌入式开发从裸机向RTOS过渡的需求,讲师详细拆解了RT-Thread的内核启动逻辑与线程管理核心概念。 + +### 1.1 裸机开发的局限性 + +**阻塞性问题:** 裸机前后台系统中,后台循环任务若存在阻塞行为(如等待标志位),会导致LCD刷新等实时任务无法及时响应。 + +**系统缺陷总结:** + +| 缺陷类型 | 具体表现 | +| :--- | :--- | +| 并发效率低 | 无法并行执行多个任务 | +| 实时性差 | 阻塞影响导致响应延迟 | +| 可维护性差 | 牵一发而动全身 | +| 可重用性差 | 模块化程度低 | + +### 1.2 RT-Thread 启动流程详解 + +**工具链入口差异:** 不同工具链(MDK/IAR/GCC)在main函数之前有不同的插桩入口函数(如MDK的`__main`、IAR的`Low Level Init`、GCC的`_entry`),RT-Thread利用这些入口进行系统初始化。 + +**启动流程序列图:** + +```mermaid +flowchart TD + A["工具链入口\n(__main / Low Level Init / _entry)"] --> B["关闭全局中断"] + B --> C["底层硬件初始化\n(UART / 时钟 / 中断控制器)"] + C --> D["打印 RT-Thread Logo"] + D --> E["初始化系统定时器"] + E --> F["初始化调度器"] + F --> G["初始化信号机制\n(可选)"] + G --> H["创建 Application 线程\n(Main 线程)"] + H --> I["创建 Timer 线程"] + I --> J["创建 Idle 线程"] + J --> K["启动调度器"] + K --> L["进入抢占式调度循环"] +``` + +**调度器启动与切换:** 调度器启动后,系统基于抢占式优先级策略查找最高优先级线程执行;若优先级相同,则基于时间片轮转调度。 + +### 1.3 线程生命周期与状态管理 + +**线程状态流转:** 线程生命周期包含初始态、就绪态、运行态、挂起态和关闭态。创建线程后需调用 `rt_thread_startup` 使其进入就绪态。 + +**线程状态流转图:** + +```mermaid +stateDiagram-v2 + [*] --> 初始态 : rt_thread_create / rt_thread_init + 初始态 --> 就绪态 : rt_thread_startup + 就绪态 --> 运行态 : 调度器选中 + 运行态 --> 就绪态 : 时间片用完 / 更高优先级线程就绪 + 运行态 --> 挂起态 : rt_thread_suspend + 挂起态 --> 就绪态 : rt_thread_resume + 运行态 --> 关闭态 : rt_thread_delete / rt_thread_exit + 关闭态 --> [*] : Idle线程回收 + 就绪态 --> 关闭态 : rt_thread_delete +``` + +**上下文切换机制:** 线程切换涉及保存上下文(压栈)和恢复上下文(弹栈),确保切换前后代码执行逻辑不被破坏。 + +**空闲线程(Idle)职责:** 优先级最低(通常为31),负责资源回收(销毁关闭态线程)及低功耗管理(如喂狗、休眠)。 + +### 1.4 线程创建方式对比 + +**静态创建(Static):** 使用 `rt_thread_init`,需预先分配栈空间数组,编译阶段即可确定RAM/Flash占用,具有确定性,适用于高安全场景。 + +**动态创建(Dynamic):** 使用 `rt_thread_create`,从系统堆分配内存,灵活性高但存在内存碎片风险,不具备确定性。 + +**创建方式对比表:** + +| 对比维度 | 静态创建 | 动态创建 | +| :--- | :--- | :--- | +| API | `rt_thread_init` | `rt_thread_create` | +| 内存分配 | 预先分配栈空间数组 | 从系统堆动态分配 | +| 确定性 | 编译阶段可确定RAM/Flash占用 | 运行时分配合并,不具备确定性 | +| 内存碎片 | 无碎片风险 | 存在内存碎片风险 | +| 适用场景 | 高安全、实时性要求高的场景 | 灵活多变、资源充足的应用场景 | +| 销毁方式 | 手动调用 `rt_thread_detach` | 自动回收或手动删除 | + +--- + +## 二、SCons 构建系统语法与应用 + +讲师介绍了RT-Thread采用的SCons构建系统,旨在简化Makefile配置,提升开发效率。 + +### 2.1 SCons 核心语法与脚本结构 + +**脚本文件构成:** 每个源码子目录通常包含SConscript脚本,用于配置当前层级源码的构建行为;rtconfig.py为顶级配置文件,定义编译器参数。 + +**路径与文件配置:** 使用 `CPPPATH` 添加头文件路径,`SRC` 列表添加源文件;可通过 `glob` 函数进行通配符匹配。 + +**依赖关系控制:** 使用 `DEPENDS` 参数定义宏依赖,仅在特定宏定义生效时,对应的源码才会参与构建。 + +**SConscript 脚本结构图:** + +```mermaid +flowchart LR + A["rtconfig.py\n(顶级配置文件)"] --> B["SConstruct\n(顶层构建脚本)"] + B --> C["SConscript\n(bsp目录)"] + B --> D["SConscript\n(packages目录)"] + B --> E["SConscript\n(src目录)"] + C --> F["CPPPATH: 头文件路径"] + C --> G["SRC: 源文件列表"] + C --> H["DEPENDS: 宏依赖控制"] + D --> F + D --> G + E --> F + E --> G +``` + +### 2.2 构建命令与工程生成 + +**常用构建命令:** + +| 命令 | 功能说明 | +| :--- | :--- | +| `scons` | 编译整个工程 | +| `scons -c` | 清理编译产物 | +| `scons --target=xxx` | 生成特定IDE工程(MDK、IAR、VS Code等) | +| `scons --dist` | 打包BSP为独立压缩包,移除无关文件 | + +**工程打包(Dist):** 使用 `scons --dist` 命令可将当前BSP打包成独立的压缩包,移除无关文件,便于独立开发。 + +### 2.3 图形化配置工具(Env/Studio) + +**菜单配置(Menuconfig):** 通过Env工具或RT-Thread Studio的图形化界面配置内核参数,配置结果自动写入 `rtconfig.h`。 + +**软件包管理:** 支持在线拉取软件包(如lvgl、kernel samples),自动集成到工程构建中。 + +**Env 工具生态图:** + +```mermaid +flowchart TD + A["RT-Thread Env 工具"] --> B["Menuconfig 图形化配置"] + A --> C["软件包管理器"] + A --> D["工程创建向导"] + A --> E["串口调试助手"] + B --> F["自动生成 rtconfig.h"] + C --> G["在线拉取软件包\nlvgl / kernel samples 等"] + C --> H["自动集成到构建系统"] + D --> I["选择芯片 / BSP"] + D --> J["生成初始工程"] + E --> K["msh 命令行交互"] + E --> L["实时数据监控"] +``` + +--- + +## 三、实操演示与调试技巧 + +通过STM32星火一号开发板演示了完整的开发流程。 + +### 3.1 工程创建与调试 + +**基于芯片创建工程:** 在Studio中基于STM32F407芯片创建工程,配置调试器(ST-Link)和串口。 + +**调试启动流程:** 通过打断点验证启动流程,从汇编入口跳转到 `_entry` 函数,依次执行硬件初始化、调度器初始化及线程创建。 + +**调试流程时序图:** + +```mermaid +sequenceDiagram + participant Developer as 开发者 + participant Studio as Studio IDE + participant STLink as ST-Link + participant MCU as STM32F407 + Developer->>Studio: 创建工程 (STM32F407) + Studio->>Developer: 配置调试器(ST-Link)和串口 + Developer->>Studio: 设置断点并下载 + Studio->>STLink: 烧录程序到 Flash + STLink->>MCU: 上电执行 + MCU->>MCU: 汇编入口跳转 _entry + MCU->>MCU: 硬件初始化 + MCU->>MCU: 调度器初始化 + MCU->>MCU: 创建 Main 线程 + Developer->>Studio: 单步执行 / 继续运行 + Studio->>STLink: 发送调试指令 + STLink->>MCU: 执行至断点 + MCU-->>STLink: 暂停并返回状态 + STLink-->>Studio: 更新调试信息 + Studio->>Developer: 显示当前执行位置与变量 +``` + +### 3.2 线程实例演示 + +**线程调度验证:** 演示了kernel samples软件包中的线程示例,展示了线程优先级调度(Thread-2优先执行)及线程退出后的自动回收。 + +**线程调度演示图:** + +```mermaid +flowchart TD + A["Thread-1\n优先级: 10"] --> B{"调度器决策"} + C["Thread-2\n优先级: 5"] --> B + B -->|"优先级更高"| D["执行 Thread-2"] + D --> E["Thread-2 运行完毕"] + E --> F["自动回收 Thread-2 资源"] + F --> G["恢复执行 Thread-1"] + G --> H["继续调度循环"] +``` + +**命令行交互:** 利用msh命令行工具查看线程列表(list_thread),验证线程状态。 + +**msh 常用命令参考:** + +| 命令 | 功能 | +| :--- | :--- | +| `list_thread` | 查看当前所有线程列表及状态 | +| `list_sem` | 查看信号量信息 | +| `list_event` | 查看事件对象 | +| `list_mutex` | 查看互斥量信息 | +| `list_msg` | 查看消息队列信息 | +| `list_mem` | 查看内存池信息 | +| `df` | 查看文件系统信息 | +| `ping` | 网络连通性测试 | + +--- + +## 四、Day 2 作业核心理论补充 + +根据作业要求,针对STM32F407启动细节、内核对象模型及调度算法进行深度梳理。 + +### 4.1 STM32F407 启动流程与 RT-Thread 引导机制 + +#### 1. 启动路径:从复位到 main() + +在STM32F407上,RT-Thread的启动调用链如下: + +1. **上电复位 (Reset_Handler)**:位于 `startup_stm32f407xx.s`。 + - 初始化栈指针 SP。 + - 复制 `.data` 段到 SRAM,清零 `.bss` 段。 + - 调用 `SystemInit` 配置时钟。 + - 跳转至 `__main` (ARMCC) 或 `_entry` (GCC)。 +2. **C 库初始化与入口**: + - 进入 `components.c` 中的 `rtthread_startup()`。 +3. **RT-Thread 内核初始化**: + - `rt_hw_interrupt_disable()`:关中断。 + - `rt_hw_board_init()`:板级初始化(SysTick, UART, Heap)。 + - `rt_show_version()`:打印版本信息。 + - `rt_timer_init()`:定时器初始化。 + - `rt_thread_init()`:调度器初始化。 + - `rt_system_signal_init()`:信号初始化(若开启)。 + - `rt_application_init()`:**创建 main 线程**(入口函数为 `main_thread_entry`)。 + - `rt_system_timer_thread_init()`:创建 timer 线程。 + - `rt_system_idle_init()`:创建 idle 线程。 + - `rt_thread_startup(&idle)`:启动 idle 线程。 + - `rt_system_scheduler_start()`:**启动调度器**(从此不再返回,跳转到最高优先级线程,即 main 线程)。 +4. **用户 main 函数**: + - `main_thread_entry()` -> `rt_application_init()` -> `main()`。 + +#### 2. 自动初始化原理 + +RT-Thread 使用 `__attribute__((section("...")))` 将初始化函数指针放入特定的只读数据段,实现类似构造函数机制。 + +- **实现机制**: + - 定义宏 `INIT_EXPORT(fn, level)`。 + - 利用 `__attribute__((section(".rti_fn." level)))` 将 `fn` 的地址存放在特定的段中。 +- **初始化等级**: + - `INIT_BOARD_EXPORT(fn)`:等级 "1"。在 `rtthread_startup` 中,`rt_hw_board_init` 之后执行。用于板级硬件驱动初始化。 + - `INIT_APP_EXPORT(fn)`:等级 "4"。在 `rt_application_init` 中,`main` 线程创建后执行。用于应用层初始化。 +- **执行流程**:内核启动时,通过遍历这些段的起始地址和结束地址,依次调用其中的函数指针。 + +### 4.2 内核基础与对象模型 + +#### 1. 面向对象的设计思想 + +RT-Thread 虽然是 C 语言编写,但通过结构体继承模拟了面向对象。 + +- **基类**:`struct rt_object`(包含名称、类型、标志、列表节点)。 +- **继承**:内核对象(线程、信号量、互斥量等)的第一个成员都是 `struct rt_object`。 +- **容器管理**:所有内核对象都挂载在 `rt_object_container` 中,通过链表管理,支持按名称查找。 + +#### 2. 线程控制块 (TCB) 关键成员 + +`struct rt_thread` 继承自 `struct rt_object`。 + +| 成员变量 | 作用描述 | +| :--- | :--- | +| `sp` (stack pointer) | **栈指针**。指向线程栈顶,上下文切换时用于保存/恢复 CPU 寄存器。 | +| `stat` (status) | **线程状态**。标识线程当前处于就绪、挂起、初始化等状态。 | +| `current_priority` | **当前优先级**。调度器依据此值决定执行顺序(数值越小优先级越高)。 | +| `init_tick` | **初始时间片**。线程每次获得 CPU 时的默认时间片长度。 | +| `remaining_tick` | **剩余时间片**。运行时递减,减为0时触发时间片轮转。 | +| `entry` | **线程入口函数**。线程启动后执行的具体任务逻辑。 | +| `parameter` | **入口参数**。传递给线程入口函数的参数。 | +| `stack_addr` / `stack_size` | 栈起始地址和大小,用于栈溢出检测。 | + +#### 3. 内存分配方式对比 + +- **静态对象**: + - **分配**:编译时分配(全局变量或静态变量)。 + - **生命周期**:永久存在,直到系统关闭。 + - **释放**:通常不需要释放,或仅从内核对象容器中脱离。 + - **场景**:高可靠场景(如航天、医疗),避免运行时内存分配失败或碎片化。 +- **动态对象**: + - **分配**:运行时从系统堆(heap)分配。 + - **生命周期**:动态创建和删除。 + - **释放**:必须调用删除接口(如 `rt_thread_delete`),由 Idle 线程回收内存。 + - **场景**:通用应用,灵活性高。 + +### 4.3 线程状态机与调度算法 + +#### 1. 五态模型与 API 触发 + +| 状态 | 描述 | 触发 API / 事件 | +| :--- | :--- | :--- | +| **初始态** | 线程已创建但未就绪 | `rt_thread_create/init` | +| **就绪态** | 线程准备好运行,等待 CPU | `rt_thread_startup` (从初始态)
`rt_thread_resume` (从挂起态)
时间片用完 (从运行态) | +| **运行态** | 线程正在占用 CPU | 调度器选中 (从就绪态) | +| **挂起态** | 线程被阻塞或主动挂起 | `rt_thread_suspend`
`rt_thread_delay`
`rt_sem_take` (等待信号量) | +| **关闭态** | 线程执行完毕或被删除 | `rt_thread_exit`
`rt_thread_delete` | + +#### 2. 调度机制剖析 + +- **抢占式调度 (Preemptive Scheduling)**: + - **原理**:系统总是执行**当前就绪状态中优先级最高**的线程。 + - **触发时机**: + 1. 当前线程优先级降低或挂起。 + 2. 有更高优先级的线程变为就绪态(如中断中释放信号量唤醒了高优先级线程)。 + - **特点**:实时性强,响应快。 + +- **时间片轮转调度 (Round-Robin Scheduling)**: + - **原理**:当多个线程**优先级相同**时,它们共享 CPU。 + - **机制**:每个线程分配一个时间片(Tick)。SysTick 中断发生时,剩余时间片减 1。当时间片减为 0,该线程被放到同优先级就绪队列的末尾,下一个线程获得执行权。 + - **特点**:保证同优先级任务的公平执行。 diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826215936098.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826215936098.png" new file mode 100644 index 0000000000000000000000000000000000000000..5e7be860becc1b0b933c1ede4220123805df0d6f Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826215936098.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220002702.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220002702.png" new file mode 100644 index 0000000000000000000000000000000000000000..661514dbf7586cc1d9dd5332358032d8c39fe01e Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220002702.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220046962.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220046962.png" new file mode 100644 index 0000000000000000000000000000000000000000..6dc49854ce7cb2570f84171cacfc314f2b3840cc Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220046962.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220223908.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220223908.png" new file mode 100644 index 0000000000000000000000000000000000000000..bee36f956965de8012398cdc1668b6c5b662a3cd Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220223908.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220241304.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220241304.png" new file mode 100644 index 0000000000000000000000000000000000000000..7e60a6fbb7f23bb2cbf8bff665e12a79d7178c64 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/FIGURES/image-20260826220241304.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/RT-Thread\350\275\257\344\273\266\345\214\205\344\270\216\346\226\207\344\273\266\347\263\273\347\273\237\345\256\236\346\210\230\345\237\271\350\256\255\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/RT-Thread\350\275\257\344\273\266\345\214\205\344\270\216\346\226\207\344\273\266\347\263\273\347\273\237\345\256\236\346\210\230\345\237\271\350\256\255\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..8c892c0aef1fc515cb3ef3cfe41e65cbceb1387a --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/day5/RT-Thread\350\275\257\344\273\266\345\214\205\344\270\216\346\226\207\344\273\266\347\263\273\347\273\237\345\256\236\346\210\230\345\237\271\350\256\255\347\254\224\350\256\260.md" @@ -0,0 +1,188 @@ +# RT-Thread 软件包与文件系统实战培训笔记 + +> **摘要**:讲解 RT-Thread 软件包索引机制,演示 AHT10 传感器与 RW007 WiFi 模块的软件包驱动配置,实现 MQTT 协议通信,并详细解析 DFS 文件系统在外部 Flash 上的挂载与应用。 + +--- + +## 知识架构全景图 + +```mermaid +graph TD + A[RT-Thread 软件包与文件系统] --> B[软件包生态与索引] + A --> C[传感器与WiFi驱动] + A --> D[MQTT协议通信] + A --> E[DFS文件系统与Flash] + A --> F[疑难解答与版本规划] + + B --> B1[Kconfig索引路径] + B --> B2[menuconfig使能] + B --> B3[依赖管理] + + C --> C1[AHT10温湿度传感器] + C --> C2[RW007 WiFi模块] + + D --> D1[EMQX服务器连接] + D --> D2[cJSON + ka_mqtt] + D --> D3[MSH命令封装] + + E --> E1[DFS + ELM/FATFS] + E --> E2[SFUD通用Flash驱动] + E --> E3[POSIX文件操作] + + F --> F1[按键中断排查] + F --> F2[env版本兼容性] + F --> F3[5.3.0版本预告] +``` + +--- + +## 一、软件包生态与索引机制 + +### 索引与配置路径 + +| 要点 | 说明 | +|------|------| +| 索引来源 | `studio安装目录/env/packages` 下的 Kconfig 配置 | +| 版本兼容 | Studio 针对内核 5.1.0 以上/以下维护两份 env 配置 | +| 仓库维护 | 独立维护在官方仓库,开发者通过提交 PR 更新 `package.json` 贡献新包 | + +### 集成流程 + +```mermaid +flowchart LR + A[menuconfig 使能软件包] --> B[自动拉取源码至工程目录] + B --> C{是否有依赖项?} + C -- 是 --> D[手动开启依赖框架] + C -- 否 --> E[编译通过] + D --> E +``` + +> **注意**:AHT10 依赖 Sensor 框架,需手动开启,否则编译报错。 + +![image-20260826215936098](FIGURES/image-20260826215936098.png) + +![image-20260826220002702](FIGURES/image-20260826220002702.png) + +--- + +## 二、传感器驱动与 WiFi 网络配置 + +### AHT10 温湿度传感器 + +```mermaid +sequenceDiagram + participant App as 应用层 + participant AHT as AHT10驱动 + participant I2C as I2C总线 + + App->>AHT: aht10_init(总线名称) + AHT->>I2C: 获取设备句柄 + App->>AHT: aht10_read_temperature() + AHT-->>App: 返回温度数据 + App->>AHT: aht10_read_humidity() + AHT-->>App: 返回湿度数据 +``` + +**关键要点:** + +- 浮点数打印需额外使能 `rt_vsnprintf_full` 软件包 + +![image-20260826220046962](FIGURES/image-20260826220046962.png) + +- 建议将读取逻辑封装在独立线程中,通过 `msh_export` 导出命令便于调试 + +### RW007 WiFi 模块 + +| 步骤 | 操作 | +|------|------| +| 引脚修正 | 根据原理图手动修正 CS、Boot0/1、中断引脚 | +| 扫描热点 | `wifi scan` | +| 连接网络 | wifi join [SSID] [PASSWORD] | +| 验证连通 | ping [netdev name] | + +![image-20260826220223908](FIGURES/image-20260826220223908.png) + +![image-20260826220241304](FIGURES/image-20260826220241304.png) + +--- + +## 三、MQTT 协议通信实现 + +```mermaid +graph LR + subgraph 设备端 + A[ka_mqtt 软件包] --> B[cJSON 数据封装] + B --> C[MQTT 客户端线程] + end + + subgraph 云端 + D[EMQX 服务器
端口 1883] + E[MQTT.fx 客户端
订阅验证] + end + + C <-->|发布/订阅| D + D <--> E +``` + +**配置要点:** + +- 使能 `cJSON` + `ka_mqtt` 软件包,开启示例代码自动生成客户端线程 +- 免费 EMQX 服务器无需校验用户名密码 +- 通过封装 MSH 命令(解析 `argc`/`argv`)实现命令行控制消息发送 + +--- + +## 四、DFS 文件系统与 Flash 管理 + +```mermaid +graph TD + subgraph 外部Flash W25Q64 + F1[固件区] + F2[下载区] + F3[文件系统区] + end + + subgraph 软件层 + S1[SFUD 通用驱动] + S2[ELM / FATFS 兼容层] + S3[DFS 设备文件系统] + end + + S3 -->|挂载到 /flash| F3 + S3 --> S2 --> S1 --> F1 + S1 --> F2 + + APP[应用层 POSIX 接口
open / write / close] --> S3 +``` + +**关键要点:** + +- **SFUD**:屏蔽不同 Flash 芯片底层差异,提供统一读写接口 +- **SPI 总线冲突**:WiFi 与 Flash 共用 SPI,需对 WiFi 片选引脚上拉处理 +- **POSIX 接口**:使用标准 `open`、`write`、`close` 操作 Flash 文件 + +--- + +## 五、疑难解答与版本规划 + +### 常见问题速查 + +| 问题 | 原因 | 解决方案 | +|------|------|----------| +| 按键中断无响应 | 代码中延时过长 | 参考按键软件包实现消抖逻辑 | +| env 版本报错 | 内核 4.1.1 不兼容新版 env | 使用 env 1.5.x 版本 | + +### 版本预告 + +> RT-Thread **5.3.0** 即将发布(距上次 5.2.2 已 10 个月),鼓励关注 Issue 并参与贡献。 + +--- + +## 核心命令速查表 + +| 命令 | 用途 | +|------|------| +| `wifi scan` | 扫描 WiFi 热点 | +| `wifi join` | 连接 WiFi 网络 | +| `ping` | 验证网络连通性 | +| `open` / `write` / `close` | POSIX 文件读写操作 |