From dad9e354a89ca702ed72d82dce9e76cb69a310d7 Mon Sep 17 00:00:00 2001 From: zqy <695961496@qq.com> Date: Fri, 25 Jul 2025 15:33:38 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../day1\347\254\224\350\256\260.md" | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day1\347\254\224\350\256\260.md" diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day1\347\254\224\350\256\260.md" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day1\347\254\224\350\256\260.md" new file mode 100644 index 0000000..6a3994c --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day1\347\254\224\350\256\260.md" @@ -0,0 +1,27 @@ +# Day1 Notes +git基本使用 +git init //初始化仓库 + +git status + +git add . //添加至暂存区 + +git commit -m "提交说明" //提交 + +git log //查看存档记录 + +git branch -a //查看当前所有分支 + +git switch master //切换分支“master” + +git checkout -b first //创建分支“first" + +git reset --hard HEAD~ //硬重置、撤销一个git命令 + +git reset --soft HEAD~ //软重置、回到上一步 + +git push origin first_notes /推送远程仓库“first_notes",或vs code上点“发布Branch” + +//------------------------------------------------ + +env编码、vscode、git bash、环境搭建 \ No newline at end of file -- Gitee From 0674877f9564e1b406a9ae3c86f2c97ff93891d8 Mon Sep 17 00:00:00 2001 From: zqy <695961496@qq.com> Date: Fri, 25 Jul 2025 21:46:54 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=A4=A9=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\344\275\234\344\270\232/day2_homework.c" | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day2_homework.c" diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day2_homework.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day2_homework.c" new file mode 100644 index 0000000..3a791bf --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day2_homework.c" @@ -0,0 +1,89 @@ + /* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-11-06 SummerGift first version + * 2018-11-19 flybreak add stm32f407-atk-explorer bsp + */ + +#include +#include +#include + +void high_prio_thread(void *parameter) +{ + int count = 0; + while(1) + { + rt_kprintf("High priority thread running - count: %d\r\n", count++); + rt_thread_mdelay(500); + } +} + +void mid_prio_thread1(void *parameter) +{ + int count = 0; + while(1) + { + rt_kprintf("Mid priority thread 1 running - count: %d\r\n", count++); + rt_thread_mdelay(100); + } +} + +void mid_prio_thread2(void *parameter) +{ + int count = 0; + while(1) + { + rt_kprintf("Mid priority thread 2 running - count: %d\r\n", count++); + rt_thread_mdelay(100); + } +} + +void low_prio_thread(void *parameter) +{ + int count = 0; + while(1) + { + rt_kprintf("Low priority thread running - count: %d\r\n", count++); + rt_thread_mdelay(1000); + } +} + +rt_thread_t high_tid = RT_NULL; +rt_thread_t mid1_tid = RT_NULL; +rt_thread_t mid2_tid = RT_NULL; +rt_thread_t low_tid = RT_NULL; + +int main(void) +{ + high_tid = rt_thread_create("high", high_prio_thread, RT_NULL, 1024, 15, 5); //高优先级抢占中、低优先级 + if(high_tid != RT_NULL) + { + rt_thread_startup(high_tid); + } + + + mid1_tid = rt_thread_create("mid1", mid_prio_thread1, RT_NULL, 1024, 10, 5); //中优先级轮换,和main同级 + if(mid1_tid != RT_NULL) + { + rt_thread_startup(mid1_tid); + } + + mid2_tid = rt_thread_create("mid2", mid_prio_thread2, RT_NULL, 1024, 10, 5); //中优先级轮换,和main同级 + if(mid2_tid != RT_NULL) + { + rt_thread_startup(mid2_tid); + } + + low_tid = rt_thread_create("low", low_prio_thread, RT_NULL, 1024, 5, 5); //低优先级,比mian低 + if(low_tid != RT_NULL) + { + rt_thread_startup(low_tid); + } + rt_kprintf("Main thread running and exiting\r\n"); + return RT_EOK; +} \ No newline at end of file -- Gitee From 1c61b263e074b2adbd36e6a633033104ebee5517 Mon Sep 17 00:00:00 2001 From: zqy <695961496@qq.com> Date: Sat, 26 Jul 2025 22:12:47 +0800 Subject: [PATCH 3/4] day3 --- .../day3_homework/event_set_sample.c" | 245 ++++++++++++ .../day3_homework/mailbox_sample.c" | 209 ++++++++++ .../day3_homework/msgq_sample.c" | 201 ++++++++++ .../day3_homework/mutex_sample.c" | 175 ++++++++ .../day3_homework/semaphore_sample.c" | 137 +++++++ .../day3_homework/signal_sample.c" | 94 +++++ .../day3\347\254\224\350\256\260.md" | 377 ++++++++++++++++++ 7 files changed, 1438 insertions(+) create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/event_set_sample.c" create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mailbox_sample.c" create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/msgq_sample.c" create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mutex_sample.c" create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/semaphore_sample.c" create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/signal_sample.c" create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day3\347\254\224\350\256\260.md" diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/event_set_sample.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/event_set_sample.c" new file mode 100644 index 0000000..7c8f728 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/event_set_sample.c" @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + */ + +/* + * 程序清单:事件集例程 + * + * 程序会初始化3个线程及一个静态事件对象 + * 线程1等待于事件对象上,处理多种事件组合; + * 线程2和线程3发送不同的事件组合 + * 展示事件集如何灵活处理多个事件的组合触发 +*/ +#include + +#define THREAD_PRIORITY 9 +#define THREAD_TIMESLICE 5 + +/* 定义多个事件标志,组成事件集 */ +#define EVENT_FLAG1 (1 << 0) // 事件1 +#define EVENT_FLAG2 (1 << 1) // 事件2 +#define EVENT_FLAG3 (1 << 2) // 事件3 +#define EVENT_FLAG4 (1 << 3) // 事件4 +#define EVENT_FLAG5 (1 << 4) // 事件5 +#define EVENT_FLAG6 (1 << 5) // 事件6 + +/* 事件控制块 */ +static struct rt_event event_set; + +/* 线程1栈空间 */ +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程1入口函数:处理各种事件组合 */ +static void thread1_handle_events(void *param) +{ + rt_uint32_t recv_events; + rt_err_t result; + + rt_kprintf("thread1: 开始处理事件集...\n"); + + // 1. 等待事件1或事件2(任意一个) + rt_kprintf("\nthread1: 等待事件1或事件2...\n"); + result = rt_event_recv(&event_set, + (EVENT_FLAG1 | EVENT_FLAG2), + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, + &recv_events); + if (result == RT_EOK) + { + rt_kprintf("thread1: 接收到事件 0x%x\n", recv_events); + if (recv_events & EVENT_FLAG1) rt_kprintf("thread1: 处理事件1的逻辑\n"); + if (recv_events & EVENT_FLAG2) rt_kprintf("thread1: 处理事件2的逻辑\n"); + } + + rt_thread_mdelay(500); + + // 2. 等待事件3和事件4(同时发生) + rt_kprintf("\nthread1: 等待事件3和事件4同时发生...\n"); + result = rt_event_recv(&event_set, + (EVENT_FLAG3 | EVENT_FLAG4), + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, + &recv_events); + if (result == RT_EOK) + { + rt_kprintf("thread1: 接收到事件 0x%x\n", recv_events); + rt_kprintf("thread1: 处理事件3和事件4同时发生的逻辑\n"); + } + + rt_thread_mdelay(500); + + // 3. 等待事件5,超时时间3秒 + rt_kprintf("\nthread1: 等待事件5(3秒超时)...\n"); + result = rt_event_recv(&event_set, + EVENT_FLAG5, + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + 3000, // 3秒超时 + &recv_events); + if (result == RT_EOK) + { + rt_kprintf("thread1: 接收到事件 0x%x\n", recv_events); + rt_kprintf("thread1: 处理事件5的逻辑\n"); + } + else if (result == -RT_ETIMEOUT) + { + rt_kprintf("thread1: 等待事件5超时!\n"); + } + + rt_thread_mdelay(500); + + // 4. 等待任意事件(事件1-6中的任何一个) + rt_kprintf("\nthread1: 等待任意事件(1-6)...\n"); + result = rt_event_recv(&event_set, + (EVENT_FLAG1 | EVENT_FLAG2 | EVENT_FLAG3 | + EVENT_FLAG4 | EVENT_FLAG5 | EVENT_FLAG6), + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, + &recv_events); + if (result == RT_EOK) + { + rt_kprintf("thread1: 接收到事件 0x%x\n", recv_events); + rt_kprintf("thread1: 处理剩余事件的逻辑\n"); + } + + rt_kprintf("\nthread1: 事件处理完成,退出\n"); +} + +/* 线程2栈空间 */ +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程2入口:发送事件组合A */ +static void thread2_send_events_a(void *param) +{ + rt_kprintf("\nthread2: 启动,开始发送事件...\n"); + + // 发送事件1 + rt_thread_mdelay(1000); + rt_kprintf("\nthread2: 发送事件1\n"); + rt_event_send(&event_set, EVENT_FLAG1); + + // 发送事件3 + rt_thread_mdelay(800); + rt_kprintf("thread2: 发送事件3\n"); + rt_event_send(&event_set, EVENT_FLAG3); + + // 发送事件4(与事件3组成AND条件) + rt_thread_mdelay(600); + rt_kprintf("thread2: 发送事件4\n"); + rt_event_send(&event_set, EVENT_FLAG4); + + // 发送事件6 + rt_thread_mdelay(1200); + rt_kprintf("thread2: 发送事件6\n"); + rt_event_send(&event_set, EVENT_FLAG6); + + rt_kprintf("thread2: 完成发送,退出\n"); +} + +/* 线程3栈空间 */ +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread3_stack[1024]; +static struct rt_thread thread3; + +/* 线程3入口:发送事件组合B */ +static void thread3_send_events_b(void *param) +{ + rt_kprintf("\nthread3: 启动,开始发送事件...\n"); + + // 等待一段时间,让线程1先进入等待状态 + rt_thread_mdelay(500); + + // 发送事件2 + rt_thread_mdelay(700); + rt_kprintf("\nthread3: 发送事件2\n"); + rt_event_send(&event_set, EVENT_FLAG2); + + // 发送事件3(再次发送) + rt_thread_mdelay(900); + rt_kprintf("thread3: 发送事件3\n"); + rt_event_send(&event_set, EVENT_FLAG3); + + // 发送事件5(但故意晚于超时时间) + rt_thread_mdelay(4000); // 超过3秒的等待时间 + rt_kprintf("thread3: 发送事件5(超时后)\n"); + rt_event_send(&event_set, EVENT_FLAG5); + + rt_kprintf("thread3: 完成发送,退出\n"); +} + +int event_set_sample(void) +{ + rt_err_t result; + + /* 初始化事件集对象 */ + result = rt_event_init(&event_set, "event_set", RT_IPC_FLAG_PRIO); + if (result != RT_EOK) + { + rt_kprintf("初始化事件集失败!\n"); + return -1; + } + + /* 初始化并启动线程1:事件处理线程 */ + rt_thread_init(&thread1, + "thread1", + thread1_handle_events, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread1); + + /* 初始化并启动线程2:事件发送线程A */ + rt_thread_init(&thread2, + "thread2", + thread2_send_events_a, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread2); + + /* 初始化并启动线程3:事件发送线程B */ + rt_thread_init(&thread3, + "thread3", + thread3_send_events_b, + RT_NULL, + &thread3_stack[0], + sizeof(thread3_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread3, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread3); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(event_set_sample, event set sample); diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mailbox_sample.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mailbox_sample.c" new file mode 100644 index 0000000..698fd08 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mailbox_sample.c" @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-24 yangjie the first version + * 2023-07-26 Modified 增强邮箱作用展示 + */ + +/* + * 程序清单:邮箱例程(增强版) + * + * 该例程创建2个线程和1个邮箱,展示邮箱如何在线程间传递不同类型的消息 + * 发送线程会发送多种类型的邮件(命令、数据、状态等) + * 接收线程会根据邮件内容进行相应处理,体现邮箱传递数据和指令的作用 + */ +#include + +#define THREAD_PRIORITY 10 +#define THREAD_TIMESLICE 5 +#define MAILBOX_SIZE 8 // 邮箱可容纳的最大邮件数 + +/* 邮箱控制块 */ +static struct rt_mailbox mb; +/* 用于放邮件的内存池 */ +static char mb_pool[MAILBOX_SIZE * sizeof(rt_ubase_t)]; + +/* 定义不同类型的邮件内容 */ +static struct mail_data { + rt_uint8_t type; // 邮件类型:1-命令 2-数据 3-状态 4-结束 + rt_uint8_t data; // 数据内容 + char desc[20]; // 描述信息 +} mail1, mail2, mail3, mail_end; + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程1入口:接收并处理邮件 */ +static void thread1_entry(void *parameter) +{ + struct mail_data *mail; + rt_err_t result; + rt_uint8_t count = 0; + + rt_kprintf("接收线程: 开始等待邮件...\n"); + + while (1) + { + /* 从邮箱中收取邮件,超时时间5秒 */ + result = rt_mb_recv(&mb, (rt_ubase_t *)&mail, 5000); + if (result == RT_EOK) + { + count++; + /* 根据邮件类型处理 */ + rt_kprintf("\n接收线程: 收到第%d封邮件\n", count); + rt_kprintf("接收线程: 类型=%d, 数据=%d, 描述=%s\n", + mail->type, mail->data, mail->desc); + + /* 如果是结束邮件则退出循环 */ + if (mail->type == 4) + { + rt_kprintf("接收线程: 收到结束邮件,准备退出\n"); + break; + } + + /* 模拟处理邮件的耗时 */ + rt_thread_mdelay(150); + } + else if (result == -RT_ETIMEOUT) + { + rt_kprintf("接收线程: 等待邮件超时\n"); + } + else + { + rt_kprintf("接收线程: 接收邮件失败,错误码: %d\n", result); + break; + } + } + + /* 执行邮箱对象脱离 */ + rt_mb_detach(&mb); + rt_kprintf("接收线程: 退出\n"); +} + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程2入口:发送邮件 */ +static void thread2_entry(void *parameter) +{ + rt_err_t result; + rt_uint8_t i; + + /* 初始化邮件内容 */ + mail1.type = 1; + mail1.data = 0x01; + rt_strncpy(mail1.desc, "启动设备", sizeof(mail1.desc)-1); + + mail2.type = 2; + mail2.data = 0x20; + rt_strncpy(mail2.desc, "温度数据", sizeof(mail2.desc)-1); + + mail3.type = 3; + mail3.data = 0x00; + rt_strncpy(mail3.desc, "运行正常", sizeof(mail3.desc)-1); + + mail_end.type = 4; + mail_end.data = 0xFF; + rt_strncpy(mail_end.desc, "任务完成", sizeof(mail_end.desc)-1); + + rt_kprintf("发送线程: 开始发送邮件...\n"); + + /* 循环发送几封不同类型的邮件 */ + for (i = 0; i < 5; i++) + { + /* 轮流发送不同类型的邮件 */ + if (i % 3 == 0) + { + result = rt_mb_send(&mb, (rt_uint32_t)&mail1); + if (result == RT_EOK) + rt_kprintf("发送线程: 发送命令邮件\n"); + } + else if (i % 3 == 1) + { + result = rt_mb_send(&mb, (rt_uint32_t)&mail2); + if (result == RT_EOK) + rt_kprintf("发送线程: 发送数据邮件\n"); + } + else + { + result = rt_mb_send(&mb, (rt_uint32_t)&mail3); + if (result == RT_EOK) + rt_kprintf("发送线程: 发送状态邮件\n"); + } + + if (result != RT_EOK) + { + rt_kprintf("发送线程: 发送邮件失败,错误码: %d\n", result); + } + + /* 间隔一段时间再发送下一封邮件 */ + rt_thread_mdelay(300); + } + + /* 发送结束邮件 */ + rt_mb_send(&mb, (rt_uint32_t)&mail_end); + rt_kprintf("发送线程: 发送结束邮件,退出\n"); +} + +int mailbox_sample(void) +{ + rt_err_t result; + + /* 初始化邮箱 */ + result = rt_mb_init(&mb, + "mbt", /* 名称是mbt */ + &mb_pool[0], /* 邮箱用到的内存池 */ + MAILBOX_SIZE, /* 邮箱可容纳的邮件数 */ + RT_IPC_FLAG_PRIO); /* 采用PRIO方式进行线程等待 */ + if (result != RT_EOK) + { + rt_kprintf("初始化邮箱失败!\n"); + return -1; + } + rt_kprintf("初始化邮箱成功,容量: %d封邮件\n", MAILBOX_SIZE); + + /* 初始化并启动接收线程 */ + rt_thread_init(&thread1, + "recv_th", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread1); + + /* 初始化并启动发送线程 */ + rt_thread_init(&thread2, + "send_th", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mailbox_sample, mailbox sample); \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/msgq_sample.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/msgq_sample.c" new file mode 100644 index 0000000..29d8af4 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/msgq_sample.c" @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-24 yangjie the first version + * 2023-07-26 Modified 增强消息队列作用展示 + */ + +/* + * 程序清单:消息队列例程(增强版) + * + * 展示消息队列的三大核心作用: + * 1. 线程间异步通信 + * 2. 消息缓冲(应对消息发送速度快于处理速度的场景) + * 3. 支持紧急消息优先处理 + */ +#include + +#define THREAD_PRIORITY 25 +#define THREAD_TIMESLICE 5 +#define MSG_QUEUE_SIZE 10 // 消息队列可容纳的最大消息数 +#define MSG_MAX_SIZE 32 // 单个消息的最大长度 + +/* 定义消息结构体,包含消息类型和内容 */ +struct message { + rt_uint8_t type; // 消息类型:1-普通 2-紧急 3-结束 + rt_uint8_t id; // 消息ID + char content[20]; // 消息内容 +}; + +/* 消息队列控制块 */ +static struct rt_messagequeue mq; +/* 消息队列中用到的内存池 */ +static rt_uint8_t msg_pool[MSG_QUEUE_SIZE * MSG_MAX_SIZE]; + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程1入口:消息处理线程 */ +static void thread1_entry(void *parameter) +{ + struct message msg; + rt_err_t result; + rt_uint8_t cnt = 0; + + rt_kprintf("处理线程: 开始等待消息...\n"); + + while (1) + { + /* 从消息队列接收消息,超时时间3秒 */ + result = rt_mq_recv(&mq, &msg, sizeof(struct message), 3000); + if (result == RT_EOK) + { + cnt++; + rt_kprintf("\n处理线程: 收到第%d条消息\n", cnt); + rt_kprintf("处理线程: 类型=%s, ID=%d, 内容=%s\n", + (msg.type == 1) ? "普通" : (msg.type == 2) ? "紧急" : "结束", + msg.id, msg.content); + + /* 如果是结束消息则退出 */ + if (msg.type == 3) + { + rt_kprintf("处理线程: 收到结束消息,准备退出\n"); + break; + } + + /* 模拟消息处理耗时(普通消息处理慢,紧急消息处理快) */ + rt_thread_mdelay(msg.type == 2 ? 100 : 300); + } + else if (result == -RT_ETIMEOUT) + { + rt_kprintf("处理线程: 等待消息超时\n"); + } + else + { + rt_kprintf("处理线程: 接收消息失败,错误码: %d\n", result); + break; + } + } + + rt_kprintf("处理线程: 完成所有消息处理,退出\n"); + rt_mq_detach(&mq); +} + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程2入口:消息发送线程 */ +static void thread2_entry(void *parameter) +{ + struct message msg; + rt_err_t result; + rt_uint8_t cnt = 0; + + rt_kprintf("发送线程: 开始发送消息...\n"); + + while (cnt < 15) // 共发送15条消息 + { + cnt++; + msg.id = cnt; + + // 每5条消息发送1条紧急消息 + if (cnt % 5 == 0) + { + msg.type = 2; + rt_sprintf(msg.content, "紧急通知 #%d", cnt); + // 发送紧急消息(插入到队列头部) + result = rt_mq_urgent(&mq, &msg, sizeof(struct message)); + rt_kprintf("发送线程: 发送紧急消息 ID=%d\n", cnt); + } + else + { + msg.type = 1; + rt_sprintf(msg.content, "普通消息 #%d", cnt); + // 发送普通消息 + result = rt_mq_send(&mq, &msg, sizeof(struct message)); + rt_kprintf("发送线程: 发送普通消息 ID=%d\n", cnt); + } + + if (result != RT_EOK) + { + rt_kprintf("发送线程: 消息发送失败 (ID=%d),错误码: %d\n", cnt, result); + } + + /* 控制发送速度:普通消息间隔短,突出队列缓冲作用 */ + rt_thread_mdelay(100); + } + + // 发送结束消息 + msg.type = 3; + msg.id = cnt++; + rt_sprintf(msg.content, "所有消息发送完毕"); + rt_mq_send(&mq, &msg, sizeof(struct message)); + rt_kprintf("发送线程: 发送结束消息,退出\n"); +} + +/* 消息队列示例初始化 */ +int msgq_sample(void) +{ + rt_err_t result; + + /* 初始化消息队列 */ + result = rt_mq_init(&mq, + "msg_q", + msg_pool, /* 消息内存池 */ + MSG_MAX_SIZE, /* 单个消息最大长度 */ + sizeof(msg_pool), /* 内存池总大小 */ + RT_IPC_FLAG_PRIO); /* 按优先级等待 */ + + if (result != RT_EOK) + { + rt_kprintf("初始化消息队列失败!\n"); + return -1; + } + rt_kprintf("初始化消息队列成功,容量: %d条消息,单条最大: %d字节\n", + MSG_QUEUE_SIZE, MSG_MAX_SIZE); + + /* 初始化并启动处理线程 */ + rt_thread_init(&thread1, + "handler", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread1); + + /* 初始化并启动发送线程 */ + rt_thread_init(&thread2, + "sender", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(msgq_sample, message queue sample); \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mutex_sample.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mutex_sample.c" new file mode 100644 index 0000000..afaad4f --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/mutex_sample.c" @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-24 yangjie the first version + * 2023-07-26 Modified 增强输出以体现互斥量作用 + */ + +/* + * 程序清单:互斥锁例程 + * + * 先演示不使用互斥锁时,number1和number2会出现不一致 + * 再演示使用互斥锁后,number1和number2始终保持一致 + * 清晰展示互斥量如何保护共享资源 + */ +#include + +#define THREAD_PRIORITY 8 +#define THREAD_TIMESLICE 5 +#define MAX_COUNT 30 // 最大计数次数 + +/* 指向互斥量的指针 */ +static rt_mutex_t dynamic_mutex = RT_NULL; +static rt_uint8_t number1, number2 = 0; +static rt_bool_t use_mutex = RT_FALSE; // 是否使用互斥锁的标志 + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread1_stack[1024]; +static struct rt_thread thread1; +static void rt_thread_entry1(void *parameter) +{ + rt_uint8_t count = 0; + + while (count < MAX_COUNT) + { + /* 根据标志决定是否使用互斥锁 */ + if (use_mutex) + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + + /* 对共享资源进行操作 */ + number1++; + rt_thread_mdelay(10); // 延迟放大线程切换概率 + number2++; + + if (use_mutex) + rt_mutex_release(dynamic_mutex); + + count++; + rt_thread_mdelay(5); // 给其他线程运行机会 + } + rt_kprintf("线程1完成操作\n"); +} + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread2_stack[1024]; +static struct rt_thread thread2; +static void rt_thread_entry2(void *parameter) +{ + rt_uint8_t count = 0; + + while (count < MAX_COUNT) + { + /* 根据标志决定是否使用互斥锁 */ + if (use_mutex) + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + + /* 检查共享资源的一致性 */ + if (number1 != number2) + { + rt_kprintf("[%s] 数据不一致! number1 = %d, number2 = %d\n", + use_mutex ? "使用互斥锁" : "不使用互斥锁", + number1, number2); + } + else if (count % 5 == 0) // 每5次输出一次正常信息 + { + rt_kprintf("[%s] 数据一致: number1 = number2 = %d\n", + use_mutex ? "使用互斥锁" : "不使用互斥锁", + number1); + } + + /* 对共享资源进行操作 */ + number1++; + number2++; + + if (use_mutex) + rt_mutex_release(dynamic_mutex); + + count++; + rt_thread_mdelay(5); // 给其他线程运行机会 + } + rt_kprintf("线程2完成操作\n"); +} + +/* 先运行无锁场景,再运行有锁场景 */ +static void run_test(const char *desc, rt_bool_t use) +{ + number1 = 0; + number2 = 0; + use_mutex = use; + + rt_kprintf("\n===== %s 测试开始 =====", desc); + rt_kprintf("\n预期结果: %s\n", + use ? "number1和number2始终相等" : "number1和number2会出现不等"); + + /* 初始化并启动线程 */ + rt_thread_init(&thread1, + "thread1", + rt_thread_entry1, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + rt_thread_entry2, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread2); + + /* 等待线程完成 */ + rt_thread_mdelay(3000); + rt_kprintf("===== %s 测试结束 =====\n", desc); +} + +/* 互斥量示例的初始化 */ +int mutex_sample(void) +{ + /* 第一步:不使用互斥锁,展示数据不一致问题 */ + run_test("不使用互斥锁", RT_FALSE); + + /* 等待输出完成 */ + rt_thread_mdelay(1000); + + /* 创建互斥锁 */ + dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO); + if (dynamic_mutex == RT_NULL) + { + rt_kprintf("创建互斥锁失败!\n"); + return -1; + } + + /* 第二步:使用互斥锁,展示数据一致性 */ + run_test("使用互斥锁", RT_TRUE); + + /* 清理资源 */ + rt_mutex_delete(dynamic_mutex); + dynamic_mutex = RT_NULL; + + rt_kprintf("\n测试结论: 互斥锁能够有效保护共享资源,确保操作的原子性\n"); + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mutex_sample, mutex sample); \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/semaphore_sample.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/semaphore_sample.c" new file mode 100644 index 0000000..d41e1dc --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/semaphore_sample.c" @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-24 yangjie the first version + * 2023-07-26 Modified 修复潜在问题,增强信号量作用展示 + */ + +#include + +#define THREAD_PRIORITY 25 +#define THREAD_TIMESLICE 5 +#define MAX_COUNT 50 // 最大计数次数 + +/* 指向信号量的指针 */ +static rt_sem_t dynamic_sem = RT_NULL; +static rt_uint8_t trigger_count = 0; // 触发信号量的计数 +static rt_uint8_t receive_count = 0; // 接收信号量的计数 + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程1:发送信号量的线程 */ +static void rt_thread1_entry(void *parameter) +{ + while (trigger_count < MAX_COUNT) + { + trigger_count++; + /* 每计数5次,发送一次信号量 */ + if (trigger_count % 5 == 0) + { + rt_kprintf("线程1: 计数到 %d,发送信号量\n", trigger_count); + rt_sem_release(dynamic_sem); + } + rt_thread_mdelay(100); // 减慢计数速度,便于观察 + } + rt_kprintf("线程1: 完成计数,退出\n"); +} + +#ifdef rt_align +rt_align(RT_ALIGN_SIZE) +#else +ALIGN(RT_ALIGN_SIZE) +#endif +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程2:接收信号量的线程 */ +static void rt_thread2_entry(void *parameter) +{ + rt_err_t result; + while (receive_count < MAX_COUNT / 5) // 接收次数是触发次数的1/5 + { + /* 等待信号量,超时时间3秒 */ + result = rt_sem_take(dynamic_sem, 3000); + if (result == RT_EOK) + { + receive_count++; + rt_kprintf("线程2: 收到信号量,累计接收 %d 次\n", receive_count); + } + else if (result == -RT_ETIMEOUT) + { + // 修复原代码未处理的超时情况 + rt_kprintf("线程2: 等待信号量超时\n"); + // 若触发计数已完成,退出等待 + if (trigger_count >= MAX_COUNT) + { + rt_kprintf("线程2: 检测到线程1已结束,退出\n"); + break; + } + } + else + { + // 修复原代码错误处理不完整的问题 + rt_kprintf("线程2: 获取信号量失败,错误码: %d\n", result); + break; + } + } +} + +/* 信号量示例的初始化 */ +int semaphore_sample() +{ + /* 创建一个动态信号量,初始值是0 */ + dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO); + if (dynamic_sem == RT_NULL) + { + rt_kprintf("创建动态信号量失败!\n"); + return -1; + } + rt_kprintf("创建动态信号量成功,初始值: 0\n"); + + /* 初始化并启动线程1 */ + rt_thread_init(&thread1, + "thread1", + rt_thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread1); + + /* 初始化并启动线程2 */ + rt_thread_init(&thread2, + "thread2", + rt_thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); +#ifdef RT_USING_SMP + rt_thread_control(&thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); +#endif + rt_thread_startup(&thread2); + + /* 等待线程执行完成后清理资源 */ + rt_thread_mdelay(MAX_COUNT * 150); // 等待足够长的时间 + rt_sem_delete(dynamic_sem); + dynamic_sem = RT_NULL; + rt_kprintf("信号量示例执行完成\n"); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(semaphore_sample, semaphore sample); \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/signal_sample.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/signal_sample.c" new file mode 100644 index 0000000..0fa8943 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\344\275\234\344\270\232/day3_homework/signal_sample.c" @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-24 yangjie the first version + * 2025-01-01 YourName Enhance signal feature demo + */ + +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; +static rt_timer_t signal_timer; // 新增定时器用于周期性发信号 + +/* 线程1的信号处理函数 */ +void thread1_signal_handler(int sig) +{ + rt_kprintf("[SIGNAL HANDLER] thread1 received signal %d\n", sig); + // 模拟信号处理耗时 + rt_thread_mdelay(300); + rt_kprintf("[SIGNAL HANDLER] Signal %d processing done\n", sig); +} + +/* 线程1的入口函数 */ +static void thread1_entry(void *parameter) +{ + int cnt = 0; + + // 安装并解除屏蔽信号 + rt_signal_install(SIGUSR1, thread1_signal_handler); + rt_signal_unmask(SIGUSR1); + + while (cnt < 20) // 延长执行次数,让信号有更多触发机会 + { + rt_kprintf("thread1 normal running: count = %d\n", cnt); + cnt++; + rt_thread_mdelay(200); // 缩短正常执行间隔,凸显信号打断 + } + rt_kprintf("thread1 finished normal loop\n"); +} + +/* 定时器回调:周期性发送信号 */ +static void signal_timer_cb(void *parameter) +{ + static int send_count = 0; + if (send_count < 3) { // 发送3次信号 + rt_thread_kill(tid1, SIGUSR1); + rt_kprintf("[TIMER] Send signal SIGUSR1 (count=%d)\n", ++send_count); + } else { + rt_timer_stop(signal_timer); // 停止定时器 + } +} + +/* 信号示例的初始化 */ +int signal_sample(void) +{ + // 创建线程1 + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid1 != RT_NULL) { + rt_thread_startup(tid1); + } else { + rt_kprintf("Create thread1 failed!\n"); + return -1; + } + + // 延迟让线程先启动 + rt_thread_mdelay(500); + + // 创建并启动定时器(周期性发信号) + signal_timer = rt_timer_create("sig-timer", + signal_timer_cb, + RT_NULL, + 1000, // 首次触发延时1s + RT_TIMER_FLAG_PERIODIC); // 周期触发 + if (signal_timer != RT_NULL) { + rt_timer_start(signal_timer); + } else { + rt_kprintf("Create signal timer failed!\n"); + return -1; + } + + return 0; +} + +MSH_CMD_EXPORT(signal_sample, signal sample); \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day3\347\254\224\350\256\260.md" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day3\347\254\224\350\256\260.md" new file mode 100644 index 0000000..46ebd0e --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day3\347\254\224\350\256\260.md" @@ -0,0 +1,377 @@ +# Day3_Notes + +信号量 (Semaphore) 是一种轻型的用于解决线程间同步问题的内核对象,一个或多个运行线程可以获取或释放它,从而达到同步或互斥的目的。用于实现任务与任务之间、任务与中断处理程序之间的同步与互斥。 + + 信号量一般分为三种: + +1.互斥信号量 用于解决互斥问题。它比较特殊,可能会引起优先级反转问题 + +2.二值信号量 用于解决同步问题 +3.计数信号量 用于解决资源计数问题 + + 二值信号量主要用于线程与线程之间、线程与中断服务程序 (ISR) 之间的同步。 + +1.用于同步的二值信号量初始值为 0,表示同步事件尚未产生; + +2.线程获取信号量以等待该同步事件的发生; + +3.另一个任务或 ISR 到达同步点时,释放信号量(将其值设置为 1)表示同步事件已发生,唤醒等待的任务。 + + 计数信号量用于控制系统中共享资源的多个实例的使用,允许多个线程同时访问同一种资源的多个实例。 +计数信号量被初始化为n(非负整数),n为该种共享资源的数目。 + + rt_sem_t rt_sem_create(const char *name, + rt_uint32_t value, + rt_uint8_t flag); +当调用这个函数时,系统将先从对象管理器中分配一个 semaphore 对象,并初始化这个对象,然后初始化父类 IPC 对象以及与 semaphore 相关的部分。在创建信号量指定的参数中,信号量标志参数决定了当信号量不可用时,多个线程等待的排队方式。当选择 RT_IPC_FLAG_FIFO(先进先出)方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当选择 RT_IPC_FLAG_PRIO(优先级等待)方式时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。 + + rt_err_t rt_sem_delete(rt_sem_t sem); + +系统不再使用信号量时,可通过删除信号量以释放系统资源。 +当调用这个函数时,系统将删除这个信号量。如果删除该信号量时,有线程正在等待该信号量,那么删除操作会先唤醒等待在该信号量上的线程(等待线程的返回值是 - RT_ERROR),然后再释放信号量的内存资源。 + + rt_err_t rt_sem_init(rt_sem_t sem, + const char *name, + rt_uint32_t value, + rt_uint8_t flag) +当调用这个函数时,系统将对这个 semaphore 对象进行初始化,然后初始化 IPC 对象以及与 semaphore 相关的部分。信号量标志可用上面创建信号量函数里提到的标志。 + + rt_err_t rt_sem_detach(rt_sem_t sem); + +脱离信号量就是让信号量对象从内核对象管理器中脱离,适用于静态初始化的信号量。 + +当调用这个函数后,内核先唤醒所有挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器中脱离。原来挂起在信号量上的等待线程将获得 - RT_ERROR 的返回值。 + + rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time); + +线程通过获取信号量来获得信号量资源实例,当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1。 + +在调用这个函数时,如果信号量的值等于零,那么说明当前信号量资源实例不可用,申请该信号量的线程将根据 time 参数的情况选择直接返回、或挂起等待一段时间、或永久等待,直到其他线程或中断释放该信号量。如果在参数 time 指定的时间内依然得不到信号量,线程将超时返回,返回值是 - RT_ETIMEOUT。 + + rt_err_t rt_sem_trytake(rt_sem_t sem); + +当用户不想在申请的信号量上挂起线程进行等待时,可以使用无等待方式获取信号量。 + +这个函数与 rt_sem_take (sem, RT_WAITING_NO) 的作用相同,即当线程申请的信号量资源实例不可用的时候,它不会等待在该信号量上,而是直接返回 - RT_ETIMEOUT。 + + rt_err_t rt_sem_release(rt_sem_t sem); +释放信号量可以唤醒挂起在该信号量上的线程 + +当信号量的值等于零时,并且有线程等待这个信号量时,释放信号量将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量;否则将把信号量的值加 1。 + + 互斥量基本概念 +互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。它和信号量不同的是,它支持: + +1.互斥量所有权:互斥量具有线程所有权,只有加锁的线程才能解锁,否则可能导致未定义行为(如死锁); + +2.递归访问; + +3.防止优先级反转的特性。 + + 优先级反转 (Priority Inversion) +所谓优先级翻转,即当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。 + + 互斥量相关函数及说明 + 创建互斥量 + rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag); + +调用 rt_mutex_create 函数创建一个互斥量,它的名字由 name 所指定。当调用这个函数时,系统将先从对象管理器中分配一个 mutex 对象,并初始化这个对象,然后初始化父类 IPC 对象以及与 mutex 相关的部分。互斥量的 flag 标志已经作废,无论用户选择 RT_IPC_FLAG_PRIO 还是 RT_IPC_FLAG_FIFO,内核均按照 RT_IPC_FLAG_PRIO 处理。 + + 删除互斥量 + rt_err_t rt_mutex_delete (rt_mutex_t mutex); + +当不再使用互斥量时,通过删除互斥量以释放系统资源,适用于动态创建的互斥量。 + +当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是 -RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间。 + + 初始化互斥量 + rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag); + +静态互斥量对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中。在使用这类静态互斥量对象前,需要先进行初始化。 + +使用该函数接口时,需指定互斥量对象的句柄(即指向互斥量控制块的指针),互斥量名称以及互斥量标志。互斥量标志可用上面创建互斥量函数里提到的标志。 + + 脱离互斥量 + rt_err_t rt_mutex_detach (rt_mutex_t mutex); + +当使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是 -RT_ERROR),然后系统将该互斥量从内核对象管理器中脱离。 + + 获取互斥量(带超时) + rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time); + +线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有。 + +如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。 + + 尝试获取互斥量(无等待) + rt_err_t rt_mutex_trytake(rt_mutex_t mutex); + +线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有。 + +如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。 + + 释放互斥量 + rt_err_t rt_mutex_release(rt_mutex_t mutex); + +当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量。 + +使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该互斥量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。 + + 事件集基本概念 +事件集是一个 32bit 的数,每个事件用一个 bit 位代表; +触发方式有与触发、或触发 + +1发送:可以从中断或者线程中进行发送 + +2接收:线程接收,条件检查(逻辑与方式、逻辑或方式) + + 事件集相关函数及说明 + 创建事件集 + rt_event_t rt_event_create(const char* name, rt_uint8_t flag); + +调用该函数接口时,系统会从对象管理器中分配事件集对象,并初始化这个对象,然后初始化父类 IPC 对象。 + + 删除事件集 + rt_err_t rt_event_delete(rt_event_t event); + +系统不再使用 rt_event_create() 创建的事件集对象时,通过删除事件集对象控制块来释放系统资源。 + +在调用 rt_event_delete 函数删除一个事件集对象时,应该确保该事件集不再被使用。在删除前会唤醒所有挂起在该事件集上的线程(线程的返回值是 -RT_ERROR),然后释放事件集对象占用的内存块。 + + 初始化事件集 + rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag); + +静态事件集对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中。在使用静态事件集对象前,需要先行对它进行初始化操作。 + +调用该接口时,需指定静态事件集对象的句柄(即指向事件集控制块的指针),然后系统会初始化事件集对象,并加入到系统对象容器中进行管理。 + + 脱离事件集 + rt_err_t rt_event_detach(rt_event_t event); + +系统不再使用 rt_event_init() 初始化的事件集对象时,通过脱离事件集对象控制块来释放系统资源。脱离事件集是将事件集对象从内核对象管理器中脱离。 + +用户调用这个函数时,系统首先唤醒所有挂在该事件集等待队列上的线程(线程的返回值是 -RT_ERROR),然后将该事件集从内核对象管理器中脱离 + + 发送事件 + rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); + +发送事件函数可以发送事件集中的一个或多个事件。 + +使用该函数接口时,通过参数 set 指定的事件标志来设定 event 事件集对象的事件标志值,然后遍历等待在 event 事件集对象上的等待线程链表,判断是否有线程的事件激活要求与当前 event 对象事件标志值匹配,如果有,则唤醒该线程。 + + 接收事件 + rt_err_t rt_event_recv(rt_event_t event, + rt_uint32_t set, + rt_uint8_t option, + rt_int32_t timeout, + rt_uint32_t* recved); + +内核使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或 “逻辑或” 来选择如何激活线程,使用 “逻辑与” 参数表示只有当所有等待的事件都发生时才激活线程,而使用 “逻辑或” 参数则表示只要有一个等待的事件发生就激活线程。 + +当用户调用这个接口时,系统首先根据 set 参数和接收选项 option 来判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位,然后返回(其中 recved 参数返回接收到的事件);如果没有发生,则把等待的 set 和 option 参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回 -RT_ETIMEOUT。 + + 消息邮箱 + +RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息。 + +非阻塞方式的邮件发送过程能够安全的应用于中断服务中,是线程、中断服务、定时器向线程发送消息的有效手段。通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。在这类情况下,只能由线程进行邮件的收取。 + +当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择等待挂起或直接返回 - RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送。 + +当一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或可以设置超时时间。当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回 - RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的 4 个字节邮件到接收缓存中。 + +在 RT-Thread 中,邮箱控制块是操作系统用于管理邮箱的一个数据结构,由结构体 struct rt_mailbox 表示。另外一种 C 表达方式 rt_mailbox_t,表示的是邮箱的句柄,在 C 语言中的实现是邮箱控制块的指针。邮箱控制块结构的详细定义请见以下代码: + + struct rt_mailbox{ + struct rt_ipc_object parent; + + rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */ + rt_uint16_t size; /* 邮箱缓冲区的大小 */ + + rt_uint16_t entry; /* 邮箱中邮件的数目 */ + rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */ + rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */};typedef struct rt_mailbox* rt_mailbox_t; + +邮箱控制块是一个结构体,其中含有事件相关的重要参数,在邮箱的功能实现中起重要的作用。邮箱的相关接口如下图所示,对一个邮箱的操作包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮箱。 + + rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag); + +创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量。 + + rt_err_t rt_mb_delete (rt_mailbox_t mb); + +当用 rt_mb_create () 创建的邮箱不再被使用时,应该删除它来释放相应的系统资源,一旦操作完成,邮箱将被永久性的删除。 + +删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 -RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。 + + rt_err_t rt_mb_init(rt_mailbox_t mb, + const char* name, + void* msgpool, + rt_size_t size, + rt_uint8_t flag) +初始化邮箱跟创建邮箱类似,只是初始化邮箱用于静态邮箱对象的初始化。与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。 + +初始化邮箱时,该函数接口需要获得用户已经申请获得的邮箱对象控制块,缓冲区的指针,以及邮箱名称和邮箱容量(能够存储的邮件数)。 + + rt_err_t rt_mb_detach(rt_mailbox_t mb); + +脱离邮箱将把静态初始化的邮箱对象从内核对象管理器中脱离。 + +使用该函数接口后,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是 - RT_ERROR),然后将该邮箱对象从内核对象管理器中脱离。 + + rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value); + +线程或者中断服务程序可以通过邮箱给其他线程发送邮件。 + +发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 -RT_EFULL 的返回值。 + + rt_err_t rt_mb_send_wait (rt_mailbox_t mb, + rt_uint32_t value, + rt_int32_t timeout); +rt_mb_send_wait () 与 rt_mb_send () 的区别在于有等待时间,如果邮箱已经满了,那么发送线程将根据设定的 timeout 参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。 + + rt_err_t rt_mb_urgent (rt_mailbox_t mb, rt_ubase_t value); + +发送紧急邮件的过程与发送邮件几乎一样,唯一的不同是,当发送紧急邮件时,邮件被直接插队放入了邮件队首,这样,接收者就能够优先接收到紧急邮件,从而及时进行处理。 + + rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout); + +只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回 RT_EOK 的返回值,否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。 + +接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回 - RT_ETIMEOUT。 + + 消息队列 + +在实际应用中我们常常遇到一些任务需要和其他任务之间进行数据交流,其实就是不同任务之间的消息传递。在一般的裸机系统中可以通过全局变量来实现不同应用程序之间的数据交互和消息传递。在操作系统中为了更好的管理不同任务之间的消息传递我们引入消息队列的概念。 + +消息队列,也就是将多条消息排成的队列形式,是一种常用的线程间通信方式,可以应用在多种场合,线程间的消息交换,使用串口接收不定长数据等。线程可以将一条或多条消息放到消息队列中,同样一个或多个线程可以从消息队列中获得消息;同时消息队列提供异步处理机制可以起到缓冲消息的作用。 + +使用消息队列实现线程间的异步通信工作,具有以下特性: + +1.支持读消息超时机制 + +2支持等待方式发送消息 + +3允许不同长度(不超过队列节点最大值)任意类型消息 + +4支持发送紧急消息 + +线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。 + +在 RT-Thread 中,消息队列控制块是操作系统用于管理消息队列的一个数据结构,由结构体 struct rt_messagequeue 表示。另外一种 C 表达方式 rt_mq_t,表示的是消息队列的句柄,在 C 语言中的实现是消息队列控制块的指针。消息队列控制块结构的详细定义请见以下代码: + + struct rt_messagequeue{ + struct rt_ipc_object parent; + + void* msg_pool; /* 指向存放消息的缓冲区的指针 */ + + rt_uint16_t msg_size; /* 每个消息的长度 */ + rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */ + + rt_uint16_t entry; /* 队列中已有的消息数 */ + + void* msg_queue_head; /* 消息链表头 */ + void* msg_queue_tail; /* 消息链表尾 */ + void* msg_queue_free; /* 空闲消息链表 */ + + rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */};typedef struct rt_messagequeue* rt_mq_t; + +消息队列控制块是一个结构体,其中含有消息队列相关的重要参数,在消息队列的功能实现中起重要的作用。消息队列的相关接口如下图所示,对一个消息队列的操作包含:创建消息队列、发送消息、接收消息、删除消息队列。 + +创建消息队列时先从对象管理器中分配一个消息队列对象,然后给消息队列对象分配一块内存空间,组织成空闲消息链表,这块内存的大小 = [消息大小 + 消息头 (用于链表连接) 的大小] * 消息队列最大个数,接着再初始化消息队列;接口返回 RT_EOK 表示动态消息队列创建成功。 + + rt_mq_t rt_mq_create(const char* name, // 消息队列名称 + rt_size_t msg_size, // 消息队列中一条消息的最大长度,单位字节 + rt_size_t max_msgs, // 消息队列的最大个数 + rt_uint8_t flag); // 消息队列采用的等待方式 + +参数 flag 可以取如下数值: RT_IPC_FLAG_FIFO (先进先出) 或 RT_IPC_FLAG_PRIO (优先级等待) + +在创建消息队列指定的参数中,事件集标志参数决定了当消息不可获取时,多个线程等待的排队方式。 + +1.当选择 RT_IPC_FLAG_FIFO(先进先出)方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的消息; + +2.当选择 RT_IPC_FLAG_PRIO(优先级等待)方式时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的消息。 + +线程或者中断服务程序都可以给消息队列发送消息。当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。发送消息的函数接口如下: + + rt_err_t rt_mq_send(rt_mq_t mq, // 消息队列对象的句柄 + void* buffer, // 消息内容 + rt_size_t size);// 消息大小 + +等待发送消息的函数接口如下,rt_mq_send_wait () 与 rt_mq_send () 的区别在于有等待时间,如果消息队列已经满了,那么发送线程将根据设定的 timeout 参数进行等待。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。 + + rt_err_t rt_mq_send_wait(rt_mq_t mq, // 消息队列对象的句柄 + const void *buffer, // 消息内容 + rt_size_t size, // 消息大小 + rt_int32_t timeout); // 超时时间 + +发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。发送紧急消息的函数接口如下: + + rt_err_t rt_mq_urgent(rt_mq_t mq, // 消息队列对象的句柄 + void* buffer, // 消息内容 + rt_size_t size); // 消息大小 + +当消息队列中有消息时,接收者才能接收消息,否则接收者会根据超时时间设置,或挂起在消息队列的等待线程队列上,或直接返回。接收消息函数接口如下 + + rt_err_t rt_mq_recv(rt_mq_t mq, // 消息队列对象的句柄 + void* buffer, // 消息内容 + rt_size_t size, // 消息大小 + rt_int32_t timeout); // 指定的超时时间 + +接收消息时,接收者需指定存储消息的消息队列对象句柄,并且指定一个内存缓冲区,接收到的消息内容将被复制到该缓冲区里。此外,还需指定未能及时取到消息时的超时时间,接收一个消息后消息队列上的队首消息被转移到了空闲消息链表的尾部。 + + 信号 + +信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求可以说是类似的。 + +信号在 RT-Thread 中用作异步通信,POSIX 标准定义了 sigset_t 类型来定义一个信号集,然而 sigset_t 类型在不同的系统可能有不同的定义方式,在 RT-Thread 中,将 sigset_t 定义成了 unsigned long 型,并命名为 rt_sigset_t,应用程序能够使用的信号为 SIGUSR1(10)和 SIGUSR2(12)。 + +信号本质是软中断,用来通知线程发生了异步事件,用做线程之间的异常通知、应急处理。一个线程不必通过任何操作来等待信号的到达,事实上,线程也不知道信号到底什么时候到达,线程之间可以互相通过调用 rt_thread_kill () 发送软中断信号。 + +收到信号的线程对各种信号有不同的处理方法,处理方法可以分为三类: + +1.第一种是类似中断的处理程序,对于需要处理的信号,线程可以指定处理函数,由该函数来处理。 + +2.第二种方法是,忽略某个信号,对该信号不做任何处理,就像未发生过一样。 + +3.第三种方法是,对该信号的处理保留系统的默认值。 + +对于信号的操作,有以下几种:安装信号、阻塞信号、阻塞解除、信号发送、信号等待。信号的接口详见下图: + +1.安装:rt_signal_install () + +2.阻塞 / 解除阻塞:rt_signal_mask/unmask () + +3.发送:rt_thread_kill () + +4.等待:rt_signal_wait () + +如果线程要处理某一信号,那么就要在线程中安装该信号。安装信号主要用来确定信号值及线程针对该信号值的动作之间的映射关系,即线程将要处理哪个信号,该信号被传递给线程时,将执行何种操作。 + + rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t[] handler); + +在信号安装时设定 handler 参数,决定了该信号的不同的处理方法。处理方法可以分为三种: + +1.类似中断的处理方式,参数指向当信号发生时用户自定义的处理函数,由该函数来处理。 + +2.参数设为 SIG_IGN,忽略某个信号,对该信号不做任何处理,就像未发生过一样。 + +3.参数设为 SIG_DFL,系统会调用默认的处理函数_signal_default_handler ()。 + +信号阻塞,也可以理解为屏蔽信号。如果该信号被阻塞,则该信号将不会递达给安装此信号的线程,也不会引发软中断处理。调 rt_signal_mask () 可以使信号阻塞: + + void rt_signal_mask(int signo); + +线程中可以安装好几个信号,使用此函数可以对其中一些信号给予 “关注”,那么发送这些信号都会引发该线程的软中断。调用 rt_signal_unmask () 可以用来解除信号阻塞: + + void rt_signal_unmask(int signo); + +当需要进行异常处理时,可以给设定了处理异常的线程发送信号,调用 rt_thread_kill () 可以用来向任何线程发送信号: + + int rt_thread_kill(rt_thread_t tid, int sig); + +等待 set 信号的到来,如果没有等到这个信号,则将线程挂起,直到等到这个信号或者等待时间超过指定的超时时间 timeout。如果等到了该信号,则将指向该信号体的指针存入 si,如下是等待信号的函数。 + + int rt_signal_wait(const rt_sigset_t *set, + rt_siginfo_t[] *si, rt_int32_t timeout); \ No newline at end of file -- Gitee From 825680110cc5c99d7a3f896aedda7ba34064c5f8 Mon Sep 17 00:00:00 2001 From: zqy <695961496@qq.com> Date: Mon, 28 Jul 2025 08:44:35 +0800 Subject: [PATCH 4/4] day4 --- .../day4\347\254\224\350\256\260.md" | 514 ++++++++++++++++++ 1 file changed, 514 insertions(+) create mode 100644 "2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day4\347\254\224\350\256\260.md" diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day4\347\254\224\350\256\260.md" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day4\347\254\224\350\256\260.md" new file mode 100644 index 0000000..d38ad09 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\221\250\345\205\250\346\235\250/\347\254\224\350\256\260/day4\347\254\224\350\256\260.md" @@ -0,0 +1,514 @@ + I/O 接口与特定 API + +I/O 提供一套接口:open、write、read、control、close + +特定 API:SPI、I2C、GPIO、RTC、WDG + +应用程序与 I/O 设备管理架构 + +核心层级:应用程序、IO 设备管理器、设备驱动 + +核心操作: + +创建设备 + +注册 IO 设备:rt_device_register () + +查找设备:rt_device_find () + +打开设备:rt_device_open () + +读数据:rt_device_read ()、read () + +关闭设备:rt_device_close () + +设备结构体定义(struct rt_device) + + struct rt_device + { + /* 内核对象基类 */ + struct rt_object parent; + /* 设备类型 */ + enum rt_device_class_type type; + /* 设备参数 */ + rt_uint16_t flag; + /* 设备打开标志 */ + rt_uint16_t open_flag; + /* 设备被引用次数 */ + rt_uint8_t ref_count; + /* 设备 ID, 0-255 */ + rt_uint8_t device_id; + /* 数据收发回调函数 */ + rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); + rt_err_t (*tx_complete) (rt_device_t dev, void *buffer); + /* 设备操作方法 */ + const struct rt_device_ops *ops; + /* 设备的私有数据 */ + void *user_data; + }; + typedef struct rt_device *rt_device_t; + + +RT-Thread 支持的 I/O 设备类型 + + 字符设备:RT_Device_Class_Char + + 块设备:RT_Device_Class_Block + + 网络接口设备:RT_Device_Class_NetIf + + 内存设备:RT_Device_Class_MTD + + RTC 设备:RT_Device_Class_RTC + + 声音设备:RT_Device_Class_Sound + + 图形设备:RT_Device_Class_Graphic + + I2C 总线设备:RT_Device_Class_I2CBUS + + USB device 设备:RT_Device_Class_USBDevice + + USB host 设备:RT_Device_Class_USBHost + + SPI 总线设备:RT_Device_Class_SPIBUS + + SPI 设备:RT_Device_Class_SPIDevice + + SDIO 设备:RT_Device_Class_SDIO + + 杂类设备:RT_Device_Class_Miscellaneous + +字符 / 块设备特点与区别 + + 字符设备: + + 提供连续的数据流,应用程序可顺序读取 + + 支持按字节 / 字符读写数据 + + 示例:键盘、串口、调制解调器 + + 块设备: + + 应用程序可随机访问设备数据,自行确定读取位置 + + 数据读写以块(通常 512B)的倍数进行 + + 不支持基于字符的寻址 + + 示例:硬盘、软盘、CD-ROM 驱动器、闪存 + + 根本区别:是否支持随机访问(字符设备只能顺序读取,块设备可随机读取) + +设备分类的原因 + + MSH 可重定向到任意字符设备(如将 LCD 模拟为字符设备,实现打印输出) + + Fatfs 文件系统依赖块设备驱动(可将 SD 卡实现为块设备,或用 RAM 模拟) + + 不同组件和应用依赖不同设备,分类可实现对一类设备的统一控制 + +1.创建设备 / 销毁设备 + + 创建设备:rt_device_t rt_device_create(int type, int attach_size); + 参数:type:设备类型(可取上述设备类型值) + attach_size:用户数据大小 + 返回: + 设备句柄:创建成功 + RT_NULL:创建失败(动态内存分配失败) + + 销毁设备:void rt_device_destroy(rt_device_t device); + 参数:device 为设备句柄 + 返回:无 + +注册 / 注销设备 + + 注册设备:rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags); + 参数:dev:设备句柄 + name:设备名称(最大长度由 rtconfig.h 中 RT_NAME_MAX 定义,多余部分自动截断) + flags:设备模式标志 + 返回: + RT_EOK:注册成功 + -RT_ERROR:注册失败(dev 为空或 name 已存在) + + 注销设备:rt_err_t rt_device_unregister(rt_device_t dev); + 参数:dev 为设备句柄 + 返回:RT_EOK 表示成功 + +3.访问 I/O 设备 + +应用程序通过 I/O 设备管理接口访问硬件设备,接口与设备操作方法的映射关系如下: + + struct rt_device_ops + { + rt_err_t (*init)(rt_device_t dev); + rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag); + rt_err_t (*close)(rt_device_t dev); + rt_size_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); + rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); + rt_err_t (*control)(rt_device_t dev, int cmd, void *args); + };设备操作方法结构体 + +映射关系 + + rt_device_init() → init() + rt_device_open() → open() + rt_device_close() → close() + rt_device_read() → read() + rt_device_write() → write() + rt_device_control() → control() + +查找设备 / 初始化设备 + + 查找设备:rt_device_t rt_device_find(const char* name); + 参数:name 为设备名称 + 返回: + 设备句柄:找到对应设备 + RT_NULL:未找到设备 + + 初始化设备:rt_err_t rt_device_init(rt_device_t dev); + 参数:dev 为设备句柄 + 返回: + RT_EOK:初始化成功 + 错误码:初始化失败 +4.打开和关闭设备 + + 打开设备:rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); + 参数: + dev:设备句柄 + oflags:设备打开模式标志 + 返回: + RT_EOK:打开成功 + -RT_EBUSY:设备注册时指定 RT_DEVICE_FLAG_STANDALONE,不允许重复打开 + 其他错误码:打开失败 + + 关闭设备:rt_err_t rt_device_close(rt_device_t dev); + 参数:dev 为设备句柄 + 返回: + RT_EOK:关闭成功 + -RT_ERROR:设备已完全关闭,不能重复关闭 + 其他错误码:关闭失败 +5.打开标志位 + +宏定义: + + RT_DEVICE_OFLAG_CLOSE(0x000):设备已关闭(内部使用) + RT_DEVICE_OFLAG_RDONLY(0x001):以只读方式打开 + RT_DEVICE_OFLAG_WRONLY(0x002):以只写方式打开 + RT_DEVICE_OFLAG_RDWR(0x003):以读写方式打开 + RT_DEVICE_OFLAG_OPEN(0x008):设备已打开(内部使用) + RT_DEVICE_FLAG_STREAM(0x040):以流模式打开 + RT_DEVICE_FLAG_INT_RX(0x100):以中断接收模式打开 + RT_DEVICE_FLAG_DMA_RX(0x200):以 DMA 接收模式打开 + RT_DEVICE_FLAG_INT_TX(0x400):以中断发送模式打开 + RT_DEVICE_FLAG_DMA_TX(0x800):以 DMA 发送模式打开 + 注意事项:上层应用需设置接收回调函数时,必须以 RT_DEVICE_FLAG_INT_RX 或 RT_DEVICE_FLAG_DMA_RX 方式打开设备,否则回调函数不生效。 +6.控制设备 + + 函数:rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); + 参数: + dev:设备句柄 + cmd:命令控制字(与设备驱动相关) + arg:控制的参数 + cmd 通用宏定义: + RT_DEVICE_CTRL_RESUME(0x01):恢复设备 + RT_DEVICE_CTRL_SUSPEND(0x02):挂起设备 + RT_DEVICE_CTRL_CONFIG(0x03):配置设备 + RT_DEVICE_CTRL_SET_INT(0x10):设置中断 + RT_DEVICE_CTRL_CLR_INT(0x11):清中断 + RT_DEVICE_CTRL_GET_INT(0x12):获取中断状态 + 返回: + RT_EOK:执行成功 + -RT_ENOSYS:dev 为空,执行失败 + 其他错误码:执行失败 +7.数据接收回调 + + 函数:rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev, rt_size_t size)); + 功能:硬件设备收到数据时,通过回调函数通知上层应用线程有数据到达 + 参数: + dev:设备句柄 + rx_ind:回调函数指针 + 返回:RT_EOK 表示设置成功 +8.数据发送完成回调 + +功能:应用程序调用 rt_device_write() 发送数据时,若底层支持自动发送,可设置回调函数,在数据发送完成后(如 DMA 传送完成、FIFO 写入完毕中断)调用 + + 函数:rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev, void *buffer)); + 参数: + dev:设备句柄 + tx_done:回调函数指针 + 返回:RT_EOK 表示设置成功 +2.1 GPIO 概念与原理 + +芯片引脚分类:电源、时钟、控制与 I/O + +I/O 口模式: + +通用输入 / 输出(GPIO) + +功能复用 I/O(如 SPI/I2C/UART 等) + +特性:多数 MCU 引脚功能可配置,通过配置切换实际功能 + +可编程控制中断:支持多种中断触发模式 + +2.2应用开发 | 常用接口 + + rt_pin_mode():设置引脚模式 + rt_pin_write():设置引脚电平 + rt_pin_read():读取引脚电平 + rt_pin_attach_irq():绑定引脚中断回调函数 + rt_pin_irq_enable():使能引脚中断 + rt_pin_detach_irq():脱离引脚中断回调函数 +2.3配置 GPIO 引脚模式 + + 函数:void rt_pin_mode(rt_base_t pin, rt_base_t mode); + mode 宏定义: + PIN_MODE_OUTPUT(0x00):输出 + PIN_MODE_INPUT(0x01):输入 + PIN_MODE_INPUT_PULLUP(0x02):上拉输入 + PIN_MODE_INPUT_PULLDOWN(0x03):下拉输入 + PIN_MODE_OUTPUT_OD(0x04):开漏输出 +2.4应用开发 | 输出高低电平 + + 函数:void rt_pin_write(rt_base_t pin, rt_base_t value); + value 宏定义: + PIN_LOW:低电平 + PIN_HIGH:高电平 +3.1 I2C 总线简介 + +定义:Inter-Integrated Circuit,由飞利浦公司于 1980 年代提出,用于连接低速外部设备 + +传输速率模式: + +低速模式:10Kbit/s + +标准模式:100Kbit/s + +快速模式:400Kbit/s + +高速模式:3.4Mbit/s + +3.2 I2C 总线协议 | 起始位和结束位 + +起始位(S):SCL 为高电平时,SDA 由高电平变为低电平 + +结束位(P):SCL 为高电平时,SDA 由低电平变为高电平 + +3.3 I2C 从机常用模式 + +向某个寄存器写 1 字节数据:改变传感器寄存器值 + +向某个寄存器写多字节数据:同上 + +从某个寄存器读 1 字节数据:读取传感器寄存器状态 + +从某个寄存器读多字节数据:读取传感器数据 + +3.4 API | 查找设备 + +函数:rt_device_t rt_device_find(const char* name); + +功能:根据 I2C 总线设备名称获取句柄,用于操作设备 + +示例: + + /* I2C 总线设备句柄 */ + struct rt_i2c_bus_device *i2c_bus; + /* 查找 I2C 总线设备,获取句柄 */ + i2c_bus = (struct rt_i2c_bus_device*)rt_device_find(name); +说明:注册的 I2C 设备名称通常为 i2c0、i2c1 等 + +3.5 API | 传输 + +函数:rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num); + +结构体 struct rt_i2c_msg: + + struct rt_i2c_msg + { + rt_uint16_t addr; /* 从机地址 */ + rt_uint16_t flags; /* 读、写标志等 */ + rt_uint16_t len; /* 数据字节数 */ + rt_uint8_t *buf; /* 读写数据缓冲区指针 */ + }; +标志 flags 宏定义: + + RT_I2C_WR(0x0000):写标志 + RT_I2C_RD:读标志 + RT_I2C_ADDR_10BIT:10 位地址模式 + RT_I2C_NO_START(1u <<4):无开始条件 + RT_I2C_IGNORE_NACK(1u <<5):忽视 NACK + RT_I2C_NO_READ_ACK(1u <<6):读时不发送 ACK +注意:函数会调用 rt_mutex_take(),不可在中断服务程序中调用,否则会报错 + +3.6 RT-Thread I2C 使用思路 + +1.查找 I2C 总线设备 + +2.构造 msgs 消息 + +3.启动 transfer 传输 + +4.处理结果 + +4.1 SPI 总线概念与原理 + +定义:Serial Peripheral Interface,高速、全双工、同步通信总线,用于短距离通讯 + +应用:连接 EEPROM、FLASH、实时时钟、AD 转换器等 + +常用 4 线:SCLK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)、CS(片选) + +4.2 概念与原理(RT-Thread 中) + +SPI 设备分类: + +SPI 总线:对应 SPI 控制器 + +SPI 设备:对应不同 CS 连接的从设备 + +使用前提:先注册 SPI 总线,再将从设备挂载到总线上 + +4.3 RT-Thread SPI 开发模式 + +1.编写 SPI BUS 驱动 + +2.注册 SPI Device 设备 + +3.打开 SPI Device 设备 + +4.使用 SPI 框架提供的 API 编程发送接收数据 + +5.关闭 SPI Device 设备 + +4.4 注册 SPI 设备 + +函数: + + rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device, const char *name, const char *bus_name, void *user_data); + +功能:将 SPI 设备挂载到指定 SPI 总线上,向内核注册设备,并保存 user_data 到控制块 + +4.6 SPI 注册设备(STM32 BSP) + +函数: + + rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_TypeDef *cs_gpiox, uint16_t cs_gpio_pin); +命名原则: + +SPI 总线:spix(如 spi0、spi1) + +SPI 设备:spixy(如 spi10 表示挂载在 spi1 总线上的 0 号设备) + +说明:user_data 通常为 SPI 设备的 CS 引脚指针,传输时控制器操作此引脚进行片选 + +4.7 控制 SPI 设备相关 API + +查找设备:rt_device_find() + +配置设备:rt_spi_configure() + +传输一次数据:rt_spi_transfer() + +发送一次数据:rt_spi_send() + +接收一次数据:rt_spi_recv() + +连续两次发送:rt_spi_send_then_send() + +先发送后接收:rt_spi_send_then_recv() + +自定义传输数据:rt_spi_transfer_message() + +注意:SPI 数据传输接口会调用 rt_mutex_take(),不可在中断服务程序中调用,否则会报错 + +4.8 查找 SPI 设备 + +函数:rt_device_t rt_device_find(const char* name); + +示例: + + #define W25Q_SPI_DEVICE_NAME "spi10" + struct rt_spi_device *spi_dev_w25q; + /* 查找 spi 设备获取句柄 */ + spi_dev_w25q = (struct rt_spi_device *)rt_device_find(W25Q_SPI_DEVICE_NAME); +说明:注册的 SPI 设备名称通常为 spi10 等 + +4.9 SPI 设备驱动配置 + +函数:rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) + +结构体 struct rt_spi_configuration: + + struct rt_spi_configuration + { + rt_uint8_t mode; /* 模式 */ + rt_uint8_t data_width; /* 数据宽度(8/16/32位) */ + rt_uint16_t reserved; /* 保留 */ + rt_uint32_t max_hz; /* 最大频率 */ + }; +模式宏定义: + +数据传输顺序: + +RT_SPI_LSB(0<<2):LSB 位在前 + +RT_SPI_MSB(1<<2):MSB 位在前 + +主从模式: + +RT_SPI_MASTER(0<<3):主设备 + +RT_SPI_SLAVE(1<<3):从设备 + +时钟极性和相位: + +RT_SPI_MODE_0(0|0):CPOL=0,CPHA=0 + +RT_SPI_MODE_1(0|RT_SPI_CPHA):CPOL=0,CPHA=1 + +RT_SPI_MODE_2(RT_SPI_CPOL|0):CPOL=1,CPHA=0 + +RT_SPI_MODE_3(RT_SPI_CPOL|RT_SPI_CPHA):CPOL=1,CPHA=1 + +其他: + +RT_SPI_CS_HIGH(1<<4):片选高电平有效 + +RT_SPI_NO_CS(1<<5):无片选 + +RT_SPI_3WIRE(1<<6):SI/SO 引脚共用 + +RT_SPI_READY(1<<7):从设备拉低暂停 + +4.10 SPI 设备传输数据(一次通信) + +函数:rt_size_t rt_spi_transfer(struct rt_spi_device *device, const void *send_buf, void *recv_buf, rt_size_t length); + +功能:传输一次数据,无需手动控制片选(开始发送时选中,返回时释放) + +说明:等同于调用 rt_spi_transfer_message() 传输一条消息 + +4.11 单独发送 / 接收数据(一次通信) + +单独发送:rt_size_t rt_spi_send(struct rt_spi_device *device, const void *send_buf, rt_size_t length);(忽略接收数据) + +单独接收:rt_size_t rt_spi_recv(struct rt_spi_device *device, void *recv_buf, rt_size_t length); + +4.12 先发送后接收数据 + +函数:rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device, const void *send_buf, rt_size_t send_length, void *recv_buf, rt_size_t recv_length); + +功能:先发送数据,再接收数据,中间不释放片选 + +说明: + +发送第一条数据时开始片选,忽略接收数据 + +发送第二条数据时,主设备发送 0XFF,接收数据保存到 recv_buf + +函数返回时释放片选 + +适合从 SPI 从设备读取数据(先发送命令和地址,再接收指定长度数据) \ No newline at end of file -- Gitee