diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/event.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/event.c" new file mode 100644 index 0000000000000000000000000000000000000000..10909f2eab9259cfd32fc678d25dc68b1caf119d --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/event.c" @@ -0,0 +1,91 @@ +#include + +#define THREAD_PRIORITY 9 +#define THREAD_TIMESLICE 5 + +#define EVENT_FLAG3 (1 << 3) +#define EVENT_FLAG5 (1 << 5) + +static struct rt_event event; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +static void thread1_recv_event(void *param) +{ + rt_uint32_t e; + + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: OR recv event 0x%x\n", e); + } + + rt_kprintf("thread1: delay 1s to prepare the second event\n"); + rt_thread_mdelay(1000); + + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: AND recv event 0x%x\n", e); + } + rt_event_detach(&event); + rt_kprintf("thread1 leave.\n"); +} + + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +static void thread2_send_event(void *param) +{ + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event5\n"); + rt_event_send(&event, EVENT_FLAG5); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_kprintf("thread2 leave.\n"); +} + +int event_sample(void) +{ + rt_err_t result; + + result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO); + if (result != RT_EOK) + { + rt_kprintf("init event failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_recv_event, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_send_event, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +MSH_CMD_EXPORT(event_sample, event sample); diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/mailbox.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/mailbox.c" new file mode 100644 index 0000000000000000000000000000000000000000..2add49c1b30b06e51fd6ac379f16b77f06531274 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/mailbox.c" @@ -0,0 +1,99 @@ + +#define THREAD_PRIORITY 10 +#define THREAD_TIMESLICE 5 + + +static struct rt_mailbox mb; + +static char mb_pool[128]; + +static char mb_str1[] = "I'm a mail!"; +static char mb_str2[] = "this is another mail!"; +static char mb_str3[] = "over"; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +static void thread1_entry(void *parameter) +{ + char *str; + + while (1) + { + rt_kprintf("thread1: try to recv a mail\n"); + + if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str); + if (str == mb_str3) + break; + + rt_thread_mdelay(100); + } + } + rt_mb_detach(&mb); +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +static void thread2_entry(void *parameter) +{ + rt_uint8_t count; + + count = 0; + while (count < 10) + { + count ++; + if (count & 0x1) + { + rt_mb_send(&mb, (rt_uint32_t)&mb_str1); + } + else + { + rt_mb_send(&mb, (rt_uint32_t)&mb_str2); + } + rt_thread_mdelay(200); + } + + rt_mb_send(&mb, (rt_uint32_t)&mb_str3); +} + +int mailbox_sample(void) +{ + rt_err_t result; + + result = rt_mb_init(&mb, + "mbt", /* 名称是 mbt */ + &mb_pool[0], /* 邮箱用到的内存池是 mb_pool */ + sizeof(mb_pool) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */ + RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */ + if (result != RT_EOK) + { + rt_kprintf("init mailbox failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + return 0; +} + +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\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/mutex.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/mutex.c" new file mode 100644 index 0000000000000000000000000000000000000000..91dbcac9f0b005f2bc52bc3a5cd82f66d4fa1733 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/mutex.c" @@ -0,0 +1,119 @@ +#include + +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; +static rt_thread_t tid3 = RT_NULL; +static rt_mutex_t mutex = RT_NULL; + + +#define THREAD_PRIORITY 10 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static void thread1_entry(void *parameter) +{ + rt_thread_mdelay(100); + + /* 此时 thread3 持有 mutex,并且 thread2 等待持有 mutex */ + + /* 检查 thread2 与 thread3 的优先级情况 */ + if (tid2->current_priority != tid3->current_priority) + { + /* 优先级不相同,测试失败 */ + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test failed.\n"); + return; + } + else + { + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test OK.\n"); + } +} + +static void thread2_entry(void *parameter) +{ + rt_err_t result; + + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + + /* 先让低优先级线程运行 */ + rt_thread_mdelay(50); + + /* + * 试图持有互斥锁,此时 thread3 持有,应把 thread3 的优先级提升 + * 到 thread2 相同的优先级 + */ + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + + if (result == RT_EOK) + { + /* 释放互斥锁 */ + rt_mutex_release(mutex); + } +} + +static void thread3_entry(void *parameter) +{ + rt_tick_t tick; + rt_err_t result; + + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + if (result != RT_EOK) + { + rt_kprintf("thread3 take a mutex, failed.\n"); + } + + /* 做一个长时间的循环,500ms */ + tick = rt_tick_get(); + while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ; + + rt_mutex_release(mutex); +} + +int pri_inversion(void) +{ + /* 创建互斥锁 */ + mutex = rt_mutex_create("mutex", RT_IPC_FLAG_PRIO); + if (mutex == RT_NULL) + { + rt_kprintf("create dynamic mutex failed.\n"); + return -1; + } + + /* 创建线程 1 */ + tid1 = rt_thread_create("thread1", + thread1_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* 创建线程 2 */ + tid2 = rt_thread_create("thread2", + thread2_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + /* 创建线程 3 */ + tid3 = rt_thread_create("thread3", + thread3_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, THREAD_TIMESLICE); + if (tid3 != RT_NULL) + rt_thread_startup(tid3); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(pri_inversion, prio_inversion sample); diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/queue.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/queue.c" new file mode 100644 index 0000000000000000000000000000000000000000..06f26458ce9721ebd77ce87d8d25771cf4e6c0b2 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/queue.c" @@ -0,0 +1,124 @@ +#include + +#define THREAD1_PRIORITY 25 +#define THREAD1_STACK_SIZE 512 +#define THREAD1_TIMESLICE 5 + +#define THREAD2_PRIORITY 25 +#define THREAD2_STACK_SIZE 512 +#define THREAD2_TIMESLICE 5 + +static struct rt_messagequeue mq; +static rt_uint8_t msg_pool[2048]; + +static rt_thread_t tid1 = RT_NULL; +static void thread1_entry(void *parameter) +{ + char buf = 0; + rt_uint8_t cnt = 0; + + while (1) + { +#if defined(RT_VERSION_CHECK) && (RTTHREAD_VERSION >= RT_VERSION_CHECK(5, 0, 1)) + if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) > 0) +#else + if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK) +#endif + { + rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf); + if (cnt == 19) + { + break; + } + } + /* 延时 50ms */ + cnt++; + rt_thread_mdelay(50); + } + rt_kprintf("thread1: detach mq \n"); + rt_mq_detach(&mq); +} + + +static rt_thread_t tid2 = RT_NULL; + +static void thread2_entry(void *parameter) +{ + int result; + char buf = 'A'; + rt_uint8_t cnt = 0; + + while (1) + { + if (cnt == 8) + { + result = rt_mq_urgent(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_urgent ERR\n"); + } + else + { + rt_kprintf("thread2: send urgent message - %c\n", buf); + } + } + else if (cnt>= 20)/* 发送 20 次消息之后退出 */ + { + rt_kprintf("message queue stop send, thread2 quit\n"); + break; + } + else + { + result = rt_mq_send(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_send ERR\n"); + } + + rt_kprintf("thread2: send message - %c\n", buf); + } + buf++; + cnt++; + rt_thread_mdelay(5); + } +} + +int msgq_sample(void) +{ + rt_err_t result; + + /* 初始化消息队列 */ + result = rt_mq_init(&mq, + "mqt", + &msg_pool[0], /* 内存池指向 msg_pool */ + 1, /* 每个消息的大小是 1 字节 */ + sizeof(msg_pool), /* 内存池的大小是 msg_pool 的大小 */ + RT_IPC_FLAG_PRIO); /* 如果有多个线程等待,优先级大小的方法分配消息 */ + + if (result != RT_EOK) + { + rt_kprintf("init message queue failed.\n"); + return -1; + } + + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD1_STACK_SIZE, + THREAD1_PRIORITY, THREAD1_TIMESLICE); + + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + tid2 = rt_thread_create("thread2", + thread2_entry, RT_NULL, + THREAD2_STACK_SIZE, + THREAD2_PRIORITY, THREAD2_TIMESLICE); + + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + return 0; +} + +MSH_CMD_EXPORT(msgq_sample, msgq sample); + diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/semaphore.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/semaphore.c" new file mode 100644 index 0000000000000000000000000000000000000000..fec41387a8c8fa15591072cb67395f3f705b2dcb --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/semaphore.c" @@ -0,0 +1,115 @@ +#include + +#define THREAD_PRIORITY 6 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +#define MAXSEM 5 + +rt_uint32_t array[MAXSEM]; + +static rt_uint32_t set, get; + +static rt_thread_t producer_tid = RT_NULL; +static rt_thread_t consumer_tid = RT_NULL; + +struct rt_semaphore sem_lock; +struct rt_semaphore sem_empty, sem_full; + +void producer_thread_entry(void *parameter) +{ + int cnt = 0; + + while (cnt < 10) + { + rt_sem_take(&sem_empty, RT_WAITING_FOREVER); + + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + array[set % MAXSEM] = cnt + 1; + rt_kprintf("the producer generates a number: %d\n", array[set % MAXSEM]); + set++; + rt_sem_release(&sem_lock); + + rt_sem_release(&sem_full); + cnt++; + + rt_thread_mdelay(20); + } + + rt_kprintf("the producer exit!\n"); + cnt = 0; +} + +void consumer_thread_entry(void *parameter) +{ + rt_uint32_t sum = 0; + + while (1) + { + rt_sem_take(&sem_full, RT_WAITING_FOREVER); + + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + sum += array[get % MAXSEM]; + rt_kprintf("the consumer[%d] get a number: %d\n", (get % MAXSEM), array[get % MAXSEM]); + get++; + rt_sem_release(&sem_lock); + + rt_sem_release(&sem_empty); + + if (get == 10) break; + + rt_thread_mdelay(50); + } + + rt_kprintf("the consumer sum is: %d\n", sum); + rt_kprintf("the consumer exit!\n"); + rt_sem_detach(&sem_lock); + rt_sem_detach(&sem_empty); + rt_sem_detach(&sem_full); + sum = 0; +} + +int producer_consumer(void) +{ + set = 0; + get = 0; + + rt_sem_init(&sem_lock, "lock", 1, RT_IPC_FLAG_PRIO); + rt_sem_init(&sem_empty, "empty", MAXSEM, RT_IPC_FLAG_PRIO); + rt_sem_init(&sem_full, "full", 0, RT_IPC_FLAG_PRIO); + + producer_tid = rt_thread_create("producer", + producer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, + THREAD_TIMESLICE); + if (producer_tid != RT_NULL) + { + rt_thread_startup(producer_tid); + } + else + { + rt_kprintf("create thread producer failed"); + return -1; + } + + consumer_tid = rt_thread_create("consumer", + consumer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, + THREAD_TIMESLICE); + if (consumer_tid != RT_NULL) + { + rt_thread_startup(consumer_tid); + } + else + { + rt_kprintf("create thread consumer failed"); + return -1; + } + + return 0; +} + +MSH_CMD_EXPORT(producer_consumer, producer_consumer sample); + diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/signal.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/signal.c" new file mode 100644 index 0000000000000000000000000000000000000000..050b44bf04de3710459bbd6a42b2d554535abefe --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/Day3/signal.c" @@ -0,0 +1,48 @@ +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; + +void thread1_signal_handler(int sig) +{ + rt_kprintf("thread1 received signal %d\n", sig); +} + +static void thread1_entry(void *parameter) +{ + int cnt = 0; + + rt_signal_install(SIGUSR1, thread1_signal_handler); + rt_signal_unmask(SIGUSR1); + + while (cnt < 10) + { + rt_kprintf("thread1 count : %d\n", cnt); + + cnt++; + rt_thread_mdelay(100); + } +} + +int signal_sample(void) +{ + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + rt_thread_mdelay(300); + + rt_thread_kill(tid1, SIGUSR1); + + return 0; +} + +MSH_CMD_EXPORT(signal_sample, signal sample); + diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/main.c" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/main.c" new file mode 100644 index 0000000000000000000000000000000000000000..5c5710972d7b7fa1e01936946ca687a182d7464e --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\344\275\234\344\270\232/main.c" @@ -0,0 +1,66 @@ +/* + * 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 + + +rt_thread_t led_thread; +rt_thread_t print_thread; +rt_thread_t thread3; + +void led_thread_task(void*parm) +{ + while(1) + { + rt_kprintf("led wink\r\n"); + rt_thread_delay(1000); + } +} +void print_thread_task(void*parm) +{ + while(1) + { + rt_kprintf("print_thread\r\n"); + rt_thread_delay(500); + } +} +void thread3_task(void*parm) +{ + while(1) + { + rt_kprintf("run the thread3\r\n"); + rt_thread_delay(500); + } +} + +int main(void) +{ + + led_thread = rt_thread_create("led_thread", led_thread_task, RT_NULL, 1024, 2, 10); + print_thread = rt_thread_create("print_thread", print_thread_task, RT_NULL, 1024, 3, 10); + thread3 = rt_thread_create("thread3", thread3_task, RT_NULL, 1024, 3 ,10); + + if(led_thread !=RT_NULL) + { + rt_thread_startup(led_thread); + } + if(print_thread !=RT_NULL) + { + rt_thread_startup(print_thread); + } + if(thread3 !=RT_NULL) + { + rt_thread_startup(thread3); + } + return RT_EOK; +} diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY1 GIT.md" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY1 GIT.md" new file mode 100644 index 0000000000000000000000000000000000000000..f1712c88f3dc98c4c4238062523e3131663bdc22 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY1 GIT.md" @@ -0,0 +1,176 @@ +### 1. 简介 + +Git是当前最先进、最主流的**分布式**版本控制系统,免费、开源!核心能力就是版本控制。任何修改历史都会被记录管理起来,可以恢复到到以前的任意时刻状态。支持跨区域多人协作编辑. + +Git是当前最先进、最主流的**分布式**版本控制系统,免费、开源!核心能力就是版本控制。再具体一点,就是面向代码文件的版本控制,代码的任何修改历史都会被记录管理起来,意味着可以恢复到到以前的任意时刻状态。支持跨区域多人协作编辑,是团队项目开发的必备基础,所以Git也就成了程序员的必备技能 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/icRxcMBeJfcica7gffGOjKOmZxDUxtCX70j5nE6x9lN2IBTXxYm0DO7JL92Vr4icZVLRjC1lBwicq5WtuOGW1KpKhQ/640?wx_fmt=png&wxfrom=13&tp=wxpic) + +### 2. 概念 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/icRxcMBeJfcica7gffGOjKOmZxDUxtCX70bEHNf1a2eicl3WOISXYibugT8VwltkBzvWf4FqOmKUI7CicRA8aoT5Uag/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1&tp=wxpic) + ++ **工作区**(Workspace)电脑里能看到的工程目录,新增、修改的文件会提交到暂存区。在这里新增文件、修改文件内容,或删除文件。 ++ **暂存区**(stage或index) 用于临时存放文件的修改,实际上上它只是一个文件(.git/index),保存待提交的文件列表信息。用git add 命令将工作区的修改保存到暂存区。 ++ **版本库/仓库**(Repository /rɪˈpɑːzətɔːri/ 仓库)Git的管理仓库,管理版本的数据库,记录文件/目录状态的地方,所有内容的修改记录(版本)都在这里。就是工作区目录下的隐藏文件夹.git,包含暂存区、分支、历史记录等信息。git commit 命令将暂存区的内容正式提交到版本库。 ++ **头**(HEAD)HEAD类似一个“指针”,指向当前活动 **分支** 的 **最新版本**。默认指向最新的master + +### 3. 安装 + +[参照此链接](https://blog.csdn.net/qq_42547733/article/details/129956784?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168621416716800188513333%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168621416716800188513333&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-129956784-null-null.142^v88^insert_down38v5,239^v2^insert_chatgpt&utm_term=git%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AE&spm=1018.2226.3001.4187) + +### 4. 版本管理(仅讲述本地版本,不包含服务器或云端) + +#### 4.1 创建版本库。 + +什么是版本库?版本库又名仓库,英文名repository,你可以简单的理解一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改,删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻还可以将文件”还原”。 + +首先要明确下,所有的版本控制系统,**只能跟踪文本文件的改动**,比如txt文件,网页,所有程序的代码等,Git也不列外,版本控制系统可以告诉你每次的改动,但**是图片,视频这些二进制文件,虽能也能由版本控制系统管理,但没法跟踪文件的变化**,只能把二进制文件每次改动串起来,也就是知道图片从1kb变成2kb,但是到底改了啥,版本控制也不知道。 + +如下我是D盘目录下新建一个testgit工程目录,现需要在这个目录下做版本管理。 + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5HicxZ1eRbXtTvZ2Qft46WlGBWIX54aR0uszSiaFIuKncPO8SuicCOEGog/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +通过命令 git init 把这个目录变成git可以管理的仓库,如下: + + + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5B5Pg32kcWqiaWe88On5EV1fDvPh0Tk2tfiaSDWYyDsUaZlhkQmmkib2Wg/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +这时候你当前testgit目录下会多一个.git的目录,这个目录是Git来跟踪管理版本的,千万不要改这个目录里面的文件,否则,会破坏git仓库。如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5kqQdLicsnIh9GnvcDWLcIia9pdNFGNiaKpKOKeiaUEPVUXRFEjTTGVawTA/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + + + +下面先看下demo如下演示: + +在版本库testgit目录下新建一个记事本文件 readme.txt 内容如下:11111111 + +1. 使用命令 git add readme.txt添加到暂存区里面去。如果和上面一样,没有任何提示,说明已经添加成功了。 + + ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5HbDUa3gjxzMPnL6CZn45eAiaaoXBPxKWKQXNLAq2lGQviaorfjov2ricg/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +2. 命令 git commit告诉Git,把文件提交到仓库。现在我们已经提交了一个readme.txt文件了 + + ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5UqCmrljibmDwu1j9ZGXlQbuib3VCZtWRWPdia1m1DYwsicbxszFALVicfxA/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +3. 查看状态,可以通过命令git status来查看是否还有文件未提交,如下: + + ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG57dngQHBvlEVs1JpLIULiaAA0VfCGk0QvEicgoJ07lLVpficfia9Bag0Elw/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + + 说明没有任何文件未提交,但是我现在继续来改下readme.txt内容,比如我在下面添加一行2222222222内容,继续使用git status来查看下结果,如下: + + ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5Sg4wDBia1EKhxnicHuj56nYemTsQiaR1eiaykATqvYdEINyd7XXandDsVw/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + + 上面的log告诉我们 readme.txt文件已被修改,但是未被提交的修改 + +4. 查看修改内容可以使用如下命令:git diff readme.txt 如下: + + ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5AwXDlSXu7ru2cBbhnXnPNuEVlDjSibRe8ZME89DNTQoK88tzBs0rpNQ/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + + 如上可以看到,readme.txt文件内容从一行11111111改成 二行 添加了一行22222222内容。 + + 知道了对readme.txt文件做了什么修改后,我们可以放心的提交到仓库了,提交修改和提交文件是一样的2步(第一步是git add 第二步是:git commit)。 + + ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5222u8ffPMNKl12tcXyBCaQjWlc6lnWpp2l3F18OPgJPAyAqpRYuMwg/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +#### 4.2 版本回退 + +如上,我们已经学会了修改文件,现在我继续对readme.txt文件进行修改,再增加一行内容为33333333333333.继续执行命令如下 + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5ibsCS5wE7BFkcwxI9X9uWJBL4iaWA9V2uUaLeM7WiaWpXayrmlHCicR4eQ/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +现在我已经对readme.txt文件做了三次修改了,那么我现在想查看下历史记录,如何查呢?我们现在可以使用命令 git log 演示如下所示: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5bK8UvEvC4JeCus9iajXfXZibqrZibobHp5XEhT0mY0icLl7LBJbxic4rrow/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +git log命令显示从最近到最远的显示日志,我们可以看到最近三次提交,最近的一次是,增加内容为333333.上一次是添加内容222222,第一次默认是 111111.如果嫌上面显示的信息太多的话,我们可以使用命令 git log –pretty=oneline 演示如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5SoF8wmUl5XI8Q7G19Ikp0fyIewjvKZYJl7IuY8ib8LjkEwPRIoQogyQ/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +现在我想使用版本回退操作,我想把当前的版本回退到上一个版本,可以使用如下2种命令,第一种是:git reset --hard HEAD^ 那么如果要回退到上上个版本只需把HEAD^ 改成 HEAD^^ 以此类推。那如果要回退到前100个版本的话,使用上面的方法肯定不方便,我们可以使用下面的简便命令操作:git reset --hard HEAD~100 即可。未回退之前的readme.txt内容如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5KD2o2JKAe9hmvn2iahSBuibzOG25AicXLItXjAOjTOXlfew8iaBnLJvYoA/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +如果想回退到上一个版本的命令如下操作: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5D4Ap4nfYD5qxxCPFw2KddwbbxicNT7yfHpAk9BSKGrpcedJrJmFuKMw/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +再来查看下 readme.txt内容如下:通过命令cat readme.txt查看 + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5gD0U32cauFtWbMpwLgfUmpC4KuP6Wiclj5XC0bl6L4oxus3FUOjA6NA/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +可以看到,内容已经回退到上一个版本了。我们可以继续使用git log 来查看下历史记录信息,如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG51EMrf3GKiaQ1HGd7NKS4pxm6d3XYSgptyBWEKicr94FFBfjVibg2GduJw/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +我们看到 增加333333 内容我们没有看到了,但是现在我想回退到最新的版本,如:有333333的内容要如何恢复呢?我们可以通过版本号回退,使用命令方法如下: + +git reset --hard 版本号 ,但是现在的问题假如我已经关掉过一次命令行或者333内容的版本号我并不知道呢?要如何知道增加3333内容的版本号呢?可以通过如下命令即可获取到版本号:git reflog 演示如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG557d9v2elWeic0fC8KmI4NuvCqib9ImyneRf9Sjyc4mBu3iaG1t8GdDSaQ/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +通过上面的显示我们可以知道,增加内容3333的版本号是 6fcfc89.我们现在可以命令 + +git reset --hard 6fcfc89来恢复了。演示如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG58ricQA1J7LtHCGIu5qCY32ibXSozwibqqRCENeyqYz8hcpFAxia5soHVOg/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +可以看到 目前已经是最新的版本了。 + +#### 4.3 Git撤销修改和删除文件操作 + +##### 4.3.1 撤销修改 + +比如我现在在readme.txt文件里面增加一行 内容为555555555555,我们先通过命令查看如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5D1XaiaBrn6nhJ8wshTrviaqIRanLC5WiaiakImsvWfrhkrgLxAUVvtZNicA/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +在我未提交之前,我发现添加5555555555555内容有误,所以我得马上恢复以前的版本,现在我可以有如下几种方法可以做修改: + +1. 如果我知道要删掉那些内容的话,直接手动更改去掉那些需要的文件,然后add添加到暂存区,最后commit掉。 +2. 我可以按以前的方法直接恢复到上一个版本。使用 git reset --hard HEAD^ + +但是现在我不想使用上面的2种方法,我想直接想使用撤销命令该如何操作呢?首先在做撤销之前,我们可以先用 git status 查看下当前的状态。如下所示: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG54zD80VuBe7kro7ibjE0wxvahYA8jsEiaHmAD4xic7xpQiaKjBXiadXCFcMw/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +可以发现,Git会告诉你,git checkout -- file 可以丢弃工作区的修改,如下命令: + +git checkout -- readme.txt,如下所示: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5Xic7fd92kJzibzb3KFyVaDZrjYSIjHiaFKnH5oS9AdDyggrmR1czjbf8Q/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +命令 git checkout --readme.txt 意思就是,把readme.txt文件在工作区做的修改全部撤销,这里有2种情况,如下: + +1. readme.txt自动修改后,还没有放到暂存区,使用 撤销修改就回到和版本库一模一样的状态。 +2. 另外一种是readme.txt已经放入暂存区了,接着又作了修改,撤销修改就回到添加暂存区后的状态 + +对于第二种情况,我想我们继续做demo来看下,假如现在我对readme.txt添加一行 内容为6666666666666,我git add 增加到暂存区后,接着添加内容7777777,我想通过撤销命令让其回到暂存区后的状态。如下所示: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5lRKfQ4JDLpdBobLgLvBk9bsPhiczQkxPRWFJS9EZ8Z3yMDZ6XiaNAQCw/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +注意:命令git checkout -- readme.txt 中的 -- 很重要,如果没有 -- 的话,那么命令变成创建分支了。 + +##### 4.3.2 删除文件 + +假如我现在版本库testgit目录添加一个文件b.txt,然后提交。如下: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5ibctkic4CtvO0Pu4iarqO36ic4ukjWB3CTGiaautj7sMhSu2PAojTIlzINA/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +如上:一般情况下,可以直接在文件目录中把文件删了,或者使用如上rm命令:rm b.txt ,如果我想彻底从版本库中删掉了此文件的话,可以再执行commit命令 提交掉,现在目录是这样的, + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5RO9TQSZxtBAbDTXrdKONlQ1sTnkO3k9M8KNYcGwTWMAaVGNw0OdicCA/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + + + +只要没有commit之前,如果我想在版本库中恢复此文件如何操作呢? + +可以使用如下命令 git checkout -- b.txt,如下所示: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5fWaGGlwiamiabDs8LOib0gUiaCrpqIDicOckbQhzX23hEQdhEtpG0EUmNzQ/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) + +再来看看我们testgit目录,添加了3个文件了。如下所示: + +![图片](https://mmbiz.qpic.cn/mmbiz_jpg/eZzl4LXykQwdIze8xZ74YQ859KfNjHG5F5tZr38F4y9GkNCibXzkHkA8Et4kPO5uPCSy117gLhw3lUMJibH3Qorw/640?wx_fmt=jpeg&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1) \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY2_TASK_note.md" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY2_TASK_note.md" new file mode 100644 index 0000000000000000000000000000000000000000..38400e75be7b020f0c6d251915be1289322a35cb --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY2_TASK_note.md" @@ -0,0 +1,169 @@ +## 一、线程基础认知 + +- **线程地位**:线程是 RT-Thread 操作系统中最小的调度单位,基于**优先级的全抢占式多线程调度算法**。 +- **调度特性**:除中断处理函数、调度器上锁代码、禁止中断代码外,系统其他部分均可被抢占(包括调度器自身)。 +- **优先级支持**:默认支持 256 个线程优先级(0 最高,255 最低),可通过配置文件改为 32 个或 8 个;最低优先级默认分配给空闲线程。 +- **同优先级调度**:相同优先级线程采用**时间片轮转调度算法**,按设定时间片轮流执行。 +- **核心作用**:将复杂应用分解为多个小的、可调度的程序单元,满足实时系统性能与时间要求(如传感器数据采集与显示的任务拆分)。 + +## 二、线程管理功能特点 + +### 1. 线程分类:两类线程均从内核对象容器分配线程对象,删除时从容器中移除 + +- **系统线程**:由 RT-Thread 内核创建(如空闲线程、主线程)。 +- **用户线程**:由应用程序创建。 + +### 2. 调度器核心工作 + +- 从就绪线程列表中查找**最高优先级线程**并调度其运行,确保高优先级线程就绪后立即获得 CPU 使用权。 +- 线程切换时,先保存当前线程上下文,恢复时读取上下文信息。 + +## 三、线程工作机制 + +### 1. 线程控制块(TCB) + +- 定义:由`struct rt_thread`表示,是操作系统管理线程的数据结构。 + +- 核心成员(部分): + + | 成员 | 作用 | + | ---------------------------------- | ----------------------------------------------------------- | + | `name` | 线程名称(长度受`RT_NAME_MAX`限制) | + | `current_priority`/`init_priority` | 当前优先级 / 初始优先级(初始优先级创建时指定,可手动修改) | + | `sp`/`stack_addr`/`stack_size` | 栈指针 / 栈地址 / 栈大小 | + | `entry` | 线程入口函数指针 | + | `stat` | 线程状态(初始、就绪、运行等) | + | `cleanup` | 线程退出时的清理函数(由空闲线程回调) | + +### 2. 线程重要属性 + +#### (1)线程栈 + +- 作用:保存线程上下文(寄存器、堆栈等)、存放函数局部变量。 +- 增长方向:与芯片架构相关(如 ARM Cortex-M 架构栈从高地址向低地址增长)。 +- 大小设置:初始可设较大值(如 1K/2K 字节),通过`list_thread`命令查看最大栈深度,加余量后确定最终大小。 + +#### (2)线程状态(5 种) + +| 状态 | 宏定义 | 说明 | +| -------- | ------------------- | ------------------------------------------------------------ | +| 初始状态 | `RT_THREAD_INIT` | 线程创建 / 初始化后未启动,不参与调度 | +| 就绪状态 | `RT_THREAD_READY` | 线程已启动,按优先级排队等待调度 | +| 运行状态 | `RT_THREAD_RUNNING` | 线程正在执行(单核系统中仅`rt_thread_self()`返回的线程处于此状态) | +| 挂起状态 | `RT_THREAD_SUSPEND` | 因等待资源或主动延时挂起,不参与调度(又称阻塞态) | +| 关闭状态 | `RT_THREAD_CLOSE` | 线程运行结束,不参与调度 | + +#### (3)线程优先级 + +- 数值越小优先级越高(0 最高),支持 256/32/8 级(可配置);ARM Cortex-M 系列常用 32 级。 +- 高优先级线程就绪时,立即抢占当前线程的 CPU 使用权。 + +#### (4)时间片 + +- 仅对同优先级就绪线程有效,约束线程单次运行时长(单位:系统节拍 OS Tick)。 +- 例:优先级相同的线程 A(时间片 10)和 B(时间片 5),轮流执行 10 节拍和 5 节拍。 + +#### (5)入口函数 + +- 无限循环模式:实时系统常用,线程被动等待事件并处理(需包含让出 CPU 的操作,如延时)。 + + ```c + void thread_entry(void* parameter) { + while (1) { + /* 等待事件并处理 */ + } + } + ``` + +- 顺序执行模式:一次性线程,执行完毕后自动删除。 + + ```c + static void thread_entry(void* parameter) { + /* 处理事务 #1 */ + /* 处理事务 #2 */ + } + ``` + +#### (6)错误码 + +| 错误码 | 含义 | +| ------------- | -------- | +| `RT_EOK` | 无错误 | +| `RT_ERROR` | 普通错误 | +| `RT_ETIMEOUT` | 超时错误 | +| `RT_ENOMEM` | 无内存 | +| `RT_EINVAL` | 非法参数 | + +### 3. 线程状态切换 + +- **初始态 → 就绪态**:调用`rt_thread_startup()`。 +- **就绪态 → 运行态**:调度器调度最高优先级就绪线程。 +- **运行态 → 挂起态**:调用`rt_thread_delay()`、获取资源失败等。 +- **挂起态 → 就绪态**:等待超时、被其他线程释放资源。 +- **运行态 / 挂起态 → 关闭态**:运行结束(自动调用`rt_thread_exit()`)、被删除 / 脱离。 + +## 四、系统线程 + +### 1. 空闲线程(idle) + +- 特性:系统创建的最低优先级线程,状态永远为就绪态。 +- 作用: + - 回收**僵尸线程**(处于关闭状态、资源未回收的线程)的资源。 + - 运行用户设置的钩子函数(如功耗管理、看门狗喂狗)。 +- 约束:必须有执行机会(其他线程不可一直死循环,需调用阻塞函数)。 + +### 2. 主线程 + +- 系统启动时创建,入口函数为`main_thread_entry()`,用户应用入口`main()`函数从此开始执行。 + +## 五、线程管理接口 + +### 1. 线程创建与删除(动态线程) + +- **创建**:`rt_thread_t rt_thread_create(...)` + - 参数:线程名、入口函数、参数、栈大小、优先级、时间片。 + - 返回:成功返回线程句柄,失败返回`RT_NULL`。 + - 说明:从动态堆分配线程句柄和栈空间(需使能`RT_USING_HEAP`)。 +- **删除**:`rt_err_t rt_thread_delete(rt_thread_t thread)` + - 作用:将线程状态改为关闭态,放入僵尸队列,由空闲线程回收资源。 + - 返回:`RT_EOK`(成功)或`-RT_ERROR`(失败)。 + +### 2. 线程初始化与脱离(静态线程) + +- **初始化**:`rt_err_t rt_thread_init(...)` + - 参数:线程句柄(用户提供)、线程名、入口函数、参数、栈起始地址、栈大小、优先级、时间片。 + - 说明:线程句柄和栈由用户提供(全局变量),需对齐(如 ARM 4 字节对齐)。 +- **脱离**:`rt_err_t rt_thread_detach(rt_thread_t thread)` + - 作用:与`rt_thread_delete`对应,用于初始化的静态线程,从队列和对象管理器中脱离。 + +### 3. 线程启动 + +- 函数:`rt_err_t rt_thread_startup(rt_thread_t thread)` +- 作用:将初始态线程转为就绪态,放入优先级队列;若优先级高于当前线程,立即切换。 + +### 4. 其他常用接口 + +| 功能 | 函数 | 说明 | +| ------------- | ------------------------------------------------------------ | ---------------------------------------------- | +| 获取当前线程 | `rt_thread_t rt_thread_self()` | 调度器未启动返回`RT_NULL` | +| 让出 CPU 资源 | `rt_thread_yield()` | 当前线程挂到就绪队列尾部,调度同优先级下一线程 | +| 线程睡眠 | `rt_thread_sleep(tick)`、`rt_thread_delay(tick)`、`rt_thread_mdelay(ms)` | 挂起指定时间后返回就绪态 | +| 线程挂起 | `rt_thread_suspend(thread)` | 仅允许挂起自身,后需调用`rt_schedule()`切换 | +| 线程恢复 | `rt_thread_resume(thread)` | 将挂起线程转为就绪态,可能触发调度 | +| 线程控制 | `rt_thread_control(thread, cmd, arg)` | 支持修改优先级、启动、关闭等(`cmd`指定操作) | + +### 5. 钩子函数 + +- **空闲钩子**:`rt_thread_idle_sethook(hook)`/`rt_thread_idle_delhook(hook)` + - 约束:钩子函数不可调用阻塞函数、内存相关函数(如`malloc`)。 +- **调度器钩子**:`void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to))` + - 作用:线程切换时调用,参数为切换前后的线程句柄。 + - 约束:不可调用系统 API,不可导致当前上下文挂起。 + +## 六、关键注意事项 + +- 优先级:数值越小优先级越高,高优先级线程可抢占低优先级线程。 +- 同优先级调度:时间片轮转,时间片单位为 OS Tick。 +- 挂起操作:仅允许线程自身挂起,不可跨线程挂起(避免系统实时性问题)。 +- 空闲线程:必须有执行机会,否则无法回收僵尸线程资源。 +- 钩子函数:需谨慎编写,避免影响系统稳定性。 \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY3_community.md" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY3_community.md" new file mode 100644 index 0000000000000000000000000000000000000000..5b758f033c2f46215561cf65635d1742764d49f5 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY3_community.md" @@ -0,0 +1,242 @@ +## 一、线程间同步机制 + +线程间同步是为解决多线程对共享资源的有序访问,避免数据不一致问题。RT-Thread 提供**信号量、互斥量、事件集**三种同步工具。 + +### 1. 信号量(Semaphore) + +#### 基础认知 + +- **核心作用**:通过计数器实现线程间同步或互斥,用于管理临界资源访问或事件通知。 +- **生活类比**:停车场管理员管理车位,空车位数量即信号量值,车辆进出对应线程获取 / 释放信号量。 + +#### 工作机制 + +- 信号量值为非负整数,代表可用资源数量: + - 线程获取信号量(`rt_sem_take`):值减 1,若值为 0 则线程挂起等待。 + - 线程释放信号量(`rt_sem_release`):值加 1,若有等待线程则唤醒优先级最高者。 +- 包含**控制块**(`struct rt_semaphore`),存储信号量值、等待线程队列等信息。 + +#### 管理接口 + +| 操作类型 | 动态接口(堆内存) | 静态接口(全局变量) | 核心功能 | +| ------------- | ------------------------ | -------------------- | --------------------------------------------------- | +| 创建 / 初始化 | `rt_sem_create(...)` | `rt_sem_init(...)` | 初始化信号量,设置初始值和等待队列策略(FIFO/PRIO) | +| 删除 / 脱离 | `rt_sem_delete(...)` | `rt_sem_detach(...)` | 释放资源,唤醒等待线程 | +| 获取信号量 | `rt_sem_take(sem, time)` | 同上 | 申请资源,超时返回`-RT_ETIMEOUT` | +| 释放信号量 | `rt_sem_release(sem)` | 同上 | 释放资源,唤醒等待线程 | +| 无等待获取 | `rt_sem_trytake(sem)` | 同上 | 不阻塞,获取失败直接返回`-RT_ETIMEOUT` | + +#### 关键特性 + +- **优先级策略**:等待队列支持`RT_IPC_FLAG_FIFO`(先进先出)或`RT_IPC_FLAG_PRIO`(优先级排序,推荐实时场景)。 + +- 适用场景 + + : + + - 线程同步:低优先级线程等待高优先级线程的事件通知。 + - 资源计数:管理有限数量的共享资源(如缓冲区、外设)。 + - 中断与线程同步:中断服务程序释放信号量,唤醒线程处理数据(如 UART 接收数据后通知线程处理)。 + +#### 应用示例 + +- **生产者 - 消费者模型**:用 3 个信号量(`lock`保护共享数组,`empty`记录空位数,`full`记录满位数),生产者获取空位后写入数据,消费者获取满位后读取数据,实现有序生产和消费。 + +### 2. 互斥量(Mutex) + +#### 基础认知 + +- **核心作用**:特殊的二值信号量(值仅 0 或 1),解决**优先级翻转问题**,支持线程递归访问临界资源。 + +- 与信号量的区别 + + : + + - 互斥量具有**所有权**:仅持有线程可释放,支持递归获取(持有计数)。 + - 优先级继承:低优先级线程持有互斥量时,若高优先级线程等待,低优先级临时提升至等待线程的最高优先级,避免被中等优先级线程抢占。 + +#### 工作机制 + +- **优先级翻转问题**:高优先级线程因等待低优先级线程占用的资源,被中等优先级线程抢占,导致响应延迟。 +- **优先级继承解决**:低优先级线程持有互斥量时,优先级临时提升至等待线程的最高优先级,释放后恢复原优先级。 + +#### 控制块与接口 + +- **控制块**:`struct rt_mutex`,包含持有线程指针、原始优先级、持有计数等。 + +- 核心接口 + + : + + | 操作 | 函数 | 说明 | + | ------------- | --------------------------------------------- | -------------------------------------------- | + | 创建 / 初始化 | `rt_mutex_create(...)`/`rt_mutex_init(...)` | 初始化互斥量,默认优先级策略为 PRIO | + | 获取 | `rt_mutex_take(mutex, time)` | 递归获取时持有计数加 1,非持有线程获取会挂起 | + | 释放 | `rt_mutex_release(mutex)` | 持有计数减 1,计数为 0 时释放,优先级恢复 | + | 删除 / 脱离 | `rt_mutex_delete(...)`/`rt_mutex_detach(...)` | 释放资源,唤醒等待线程 | + +#### 适用场景 + +- 多线程访问共享资源且可能引发优先级翻转的场景(如传感器数据处理、外设控制)。 +- 线程需递归访问临界资源的场景(如嵌套函数调用共享数据)。 + +### 3. 事件集(Event) + +#### 基础认知 + +- **核心作用**:通过 32 位整数的 bit 位表示事件,支持**多事件组合触发**(逻辑与 / 或),实现一对多或多对多同步。 +- **生活类比**:等待公交时,可等待 “任意一辆直达公交”(逻辑或)或 “公交 + 同伴到达”(逻辑与)。 + +#### 工作机制 + +- **事件表示**:32 位无符号整数(`rt_uint32_t`),每 bit 代表一个事件(0~31)。 + +- 触发方式 + + : + + - `RT_EVENT_FLAG_OR`:任意关注事件发生即触发。 + - `RT_EVENT_FLAG_AND`:所有关注事件同时发生才触发。 + +- **无排队性**:同一事件多次发送(未被处理)仅等效一次。 + +#### 核心接口 + +| 操作 | 函数 | 关键参数 | 说明 | +| ------------- | ---------------------------------------------------- | -------------------------------------------- | ------------------------------------ | +| 创建 / 初始化 | `rt_event_create(...)`/`rt_event_init(...)` | `flag`(FIFO/PRIO) | 初始化事件集 | +| 发送事件 | `rt_event_send(event, set)` | `set`:事件 bit 掩码(如`1<<3`表示事件 3) | 置位对应事件 bit,唤醒满足条件的线程 | +| 接收事件 | `rt_event_recv(event, set, option, timeout, recved)` | `option`:`OR`/`AND`+`CLEAR`(是否清除事件) | 等待事件,`recved`返回实际触发的事件 | +| 删除 / 脱离 | `rt_event_delete(...)`/`rt_event_detach(...)` | - | 释放资源,唤醒等待线程 | + +#### 应用示例 + +- 线程 1 等待 “事件 3 或事件 5”(`RT_EVENT_FLAG_OR`),线程 2 发送事件 3 后线程 1 被唤醒;之后线程 1 等待 “事件 3 且事件 5”(`RT_EVENT_FLAG_AND`),线程 2 发送事件 5 和 3 后线程 1 再次被唤醒。 + +#### 适用场景 + +- 线程需响应多个事件的场景(如智能家居中控等待 “门开 + 灯亮” 信号)。 +- 多线程协同完成复杂任务(如数据采集线程 + 处理线程 + 上传线程的事件联动)。 + +## 二、线程间通信机制 + +线程间通信用于传递数据或消息,RT-Thread 提供**邮箱、消息队列、信号**三种通信工具。 + +### 1. 邮箱(Mailbox) + +#### 基础认知 + +- **核心作用**:传递 4 字节消息(32 位系统中可存指针),适用于短消息或指针传递,开销低、效率高。 +- **特点**:支持缓存一定数量消息(容量固定),非阻塞发送可用于中断。 + +#### 工作机制 + +- **消息存储**:环形缓冲区,每个消息占 4 字节,`in_offset`/`out_offset`记录读写位置。 + +- 发送与接收 + + : + + - 发送(`rt_mb_send`):消息存入缓冲区,满则返回`-RT_EFULL`。 + - 接收(`rt_mb_recv`):从缓冲区取消息,空则挂起等待超时。 + +- **紧急消息**:`rt_mb_urgent`发送消息至队首,优先被处理。 + +#### 核心接口 + +| 操作 | 函数 | 说明 | +| ------------- | -------------------------------------------------- | --------------------------------------------- | +| 创建 / 初始化 | `rt_mb_create(name, size, flag)`/`rt_mb_init(...)` | `size`为消息数量(缓冲区大小 / 4) | +| 发送消息 | `rt_mb_send(mb, value)`/`rt_mb_send_wait(...)` | `value`为 4 字节消息,`send_wait`支持超时等待 | +| 接收消息 | `rt_mb_recv(mb, &value, timeout)` | 接收消息存入`value`,超时返回`-RT_ETIMEOUT` | + +#### 适用场景 + +- 短消息传递(如状态码、命令字)。 +- 中断向线程发送消息(如外设中断通知线程处理数据)。 +- 传递指针(指向大缓冲区,避免拷贝开销)。 + +### 2. 消息队列(Message Queue) + +#### 基础认知 + +- **核心作用**:传递不定长消息(长度可配置),是邮箱的扩展,支持缓存多条消息。 +- **特点**:消息内容直接拷贝,无需动态内存分配,支持紧急消息插队。 + +#### 工作机制 + +- **消息结构**:每条消息含消息体 + 消息头(链表节点),缓冲区大小 =(消息大小 + 消息头大小)× 最大消息数。 + +- 发送与接收 + + : + + - 发送(`rt_mq_send`):消息拷贝至空闲缓冲区,满则返回`-RT_EFULL`。 + - 紧急发送(`rt_mq_urgent`):消息插入队首,优先被接收。 + - 接收(`rt_mq_recv`):从队首取消息,空则挂起等待。 + +#### 核心接口 + +| 操作 | 函数 | 关键参数 | 说明 | +| ------------- | --------------------------------------- | -------------------------------------------------------- | -------------------------------- | +| 创建 / 初始化 | `rt_mq_create(...)`/`rt_mq_init(...)` | `msg_size`(单条消息最大长度)、`max_msgs`(最大消息数) | 初始化消息队列 | +| 发送消息 | `rt_mq_send(mq, buffer, size)` | `buffer`为消息内容,`size`为消息长度(≤`msg_size`) | 发送普通消息 | +| 接收消息 | `rt_mq_recv(mq, buffer, size, timeout)` | `buffer`存储接收内容,返回实际接收长度 | 接收消息,超时返回`-RT_ETIMEOUT` | + +#### 应用示例 + +- 线程 2 发送普通消息(A、B、C...)和紧急消息(I),线程 1 接收时紧急消息 “I” 优先于后续消息被处理,体现插队特性。 + +#### 适用场景 + +- 不定长消息传递(如串口接收的可变长度数据)。 +- 多线程间复杂数据交换(如传感器数据帧、日志信息)。 + +### 3. 信号(Signal) + +#### 基础认知 + +- **核心作用**:模拟软中断,用于异步事件通知(如异常处理、应急响应),线程无需主动等待。 +- **特点**:RT-Thread 支持`SIGUSR1`(10)和`SIGUSR2`(12)两个用户信号,需在 menuconfig 中使能。 + +#### 工作机制 + +- **信号处理**:线程可安装信号处理函数(自定义 / 忽略 / 默认),收到信号时触发处理。 +- **优先级**:信号处理函数在当前线程栈上执行,中断线程正常流程。 + +#### 核心接口 + +| 操作 | 函数 | 说明 | +| -------- | ------------------------------------ | --------------------------------------------------- | +| 安装信号 | `rt_signal_install(signo, handler)` | `handler`为处理函数(`SIG_IGN`忽略,`SIG_DFL`默认) | +| 发送信号 | `rt_thread_kill(tid, sig)` | 向线程`tid`发送信号`sig` | +| 等待信号 | `rt_signal_wait(&set, &si, timeout)` | 等待`set`中的信号,超时返回`-RT_ETIMEOUT` | + +#### 适用场景 + +- 线程异常处理(如错误恢复、紧急退出)。 +- 异步事件通知(如外部触发的应急处理)。 + +## 三、同步与通信机制对比 + +| 工具 | 核心特点 | 典型应用 | 优势 | 局限 | +| -------- | ------------------------- | -------------------------------------- | ------------------------ | ---------------------- | +| 信号量 | 计数器,多资源管理 | 临界资源访问、事件通知 | 轻量,支持多线程等待 | 无法解决优先级翻转 | +| 互斥量 | 二值信号量,优先级继承 | 共享资源访问(避免优先级翻转) | 解决优先级翻转,支持递归 | 不可用于中断 | +| 事件集 | 多事件组合触发(与 / 或) | 多条件同步(如 “数据就绪 + 权限允许”) | 灵活处理多事件 | 事件无排队性 | +| 邮箱 | 4 字节消息,效率高 | 短消息、指针传递 | 轻量,支持中断发送 | 消息长度固定(4 字节) | +| 消息队列 | 不定长消息,直接拷贝 | 变长数据传递(如串口数据) | 支持任意长度消息 | 开销较邮箱大 | +| 信号 | 软中断,异步通知 | 异常处理、应急响应 | 异步触发,无需主动等待 | 仅支持 2 个用户信号 | + +## 四、总结 + +RT-Thread 提供的同步与通信工具覆盖了从简单到复杂的线程协作场景: + + + +- **同步**优先选择:互斥量(共享资源 + 优先级敏感)、事件集(多条件)、信号量(资源计数)。 +- **通信**优先选择:消息队列(不定长消息)、邮箱(短消息 / 指针)、信号(异步事件)。 + + + +实际开发中需根据**资源开销、消息长度、同步条件**选择合适工具,确保系统实时性与可靠性。 \ No newline at end of file diff --git "a/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY4 IOmodule.md" "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY4 IOmodule.md" new file mode 100644 index 0000000000000000000000000000000000000000..f1c0a8e2e89bc2b52163a661bc483e740494cd68 --- /dev/null +++ "b/2025/\347\254\2546\347\273\204(GD32F527I-EVAL)/\345\217\266\350\247\202\345\213\207/\347\254\224\350\256\260/\343\200\220RSOC25\343\200\221DAY4 IOmodule.md" @@ -0,0 +1,345 @@ +## 一、I/O 设备模型概述 + +RT-Thread 的 I/O 设备模型是硬件与应用之间的抽象层,通过分层设计屏蔽硬件差异,实现统一访问接口。其核心是 **“分层架构”**与**“对象抽象”**,让应用程序无需关注硬件细节,直接通过标准接口操作设备。 + +### 1. 模型框架(三层结构) + +| 层级 | 作用 | 核心组件 | +| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| **I/O 设备管理层** | 提供统一的设备访问接口(如查找、打开、读写),管理设备注册与生命周期 | `rt_device_find()`、`rt_device_open()`、`rt_device_read()` 等 | +| **设备驱动框架层** | 对同类设备进行抽象,抽取共性逻辑,定义标准驱动接口 | 串口框架(`serial`)、I2C 框架(`i2c`)等 | +| **设备驱动层** | 实现具体硬件的操作(寄存器配置、中断处理等),对接驱动框架 | 芯片级驱动(如 `drv_gpio.c`、`drv_usart.c`) | + + + +**核心目标**:应用程序通过统一接口访问不同设备,驱动层负责硬件细节,实现 “一次开发,多平台适配”。 + +### 2. 设备对象(`struct rt_device`) + +设备对象是 I/O 设备模型的核心,所有设备均以对象形式存在,继承自内核对象,包含设备类型、操作方法等关键信息。 + +#### 核心结构定义 + +c + + + + + + + + + + + +```c +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; /* 引用计数(打开次数) */ + rt_uint8_t device_id; /* 设备ID(0-255) */ + + /* 数据收发回调(用于异步通知) */ + 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; /* 私有数据(如硬件配置信息) */ +}; +``` + +#### 设备操作集(`struct rt_device_ops`) + +驱动层必须实现的设备操作接口,是应用层与硬件交互的桥梁: + + + +c + + + + + + + + + + + +```c +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); /* 控制设备(如配置参数) */ +}; +``` + +### 3. 设备类型与注册流程 + +#### 设备类型 + +RT-Thread 支持多种设备类型,常见类型如下: + + + +c + + + + + + + + + + + +```c +#define RT_Device_Class_Char 0x0001 /* 字符设备(如串口、按键) */ +#define RT_Device_Class_Block 0x0002 /* 块设备(如SD卡、Flash) */ +#define RT_Device_Class_NetIf 0x0003 /* 网络接口设备(如以太网) */ +#define RT_Device_Class_Miscellaneous 0x0004 /* 杂类设备(如看门狗、PIN设备) */ +``` + +#### 设备注册流程 + +1. **驱动层**:创建设备实例,实现 `struct rt_device_ops` 操作集; +2. **注册到系统**:通过 `rt_device_register()` 将设备对象加入设备管理器,指定设备名称和类型; +3. **应用层访问**:通过 `rt_device_find()` 查找设备,调用 `rt_device_open()` 等接口操作。 + + + +**示例(看门狗设备注册)**: + + + +c + + + + + + + + + + + +```c +// 1. 定义操作集 +const static struct rt_device_ops wdt_ops = { + rt_watchdog_init, // 初始化 + rt_watchdog_open, // 打开 + rt_watchdog_close, // 关闭 + RT_NULL, // 读(未实现) + RT_NULL, // 写(未实现) + rt_watchdog_control // 控制(如喂狗、设置超时) +}; + +// 2. 注册设备 +rt_err_t rt_hw_watchdog_register(...) { + struct rt_device *device = &(wtd->parent); + device->type = RT_Device_Class_Miscellaneous; + device->ops = &wdt_ops; + return rt_device_register(device, "wdt", 0); // 注册为"wdt"设备 +} +``` + +## 二、设备访问核心接口 + +应用程序通过以下接口访问设备,接口与设备操作集一一映射: + + + +| 功能 | 接口 | 对应设备操作集方法 | 说明 | +| ---------- | ------------------------------------------------------------ | ------------------ | ------------------------------------------ | +| 查找设备 | `rt_device_t rt_device_find(const char* name)` | - | 根据设备名称获取设备句柄 | +| 初始化设备 | `rt_err_t rt_device_init(rt_device_t dev)` | `ops->init` | 初始化硬件(如引脚配置、时钟使能) | +| 打开设备 | `rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)` | `ops->open` | 使能设备,设置打开模式(如读写、中断模式) | +| 关闭设备 | `rt_err_t rt_device_close(rt_device_t dev)` | `ops->close` | 禁用设备,引用计数减为 0 时完全关闭 | +| 读数据 | `rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)` | `ops->read` | 从设备读取数据到缓冲区 | +| 写数据 | `rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)` | `ops->write` | 将缓冲区数据写入设备 | +| 控制设备 | `rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg)` | `ops->control` | 发送控制命令(如设置波特率、喂狗) | + +## 三、PIN 设备及其与 I/O 模型的对接 + +PIN 设备(通用 I/O 引脚)是最基础的 I/O 设备,用于控制 LED、按键、传感器等简单外设。PIN 设备通过驱动层实现对接 I/O 模型,提供电平读写、中断等功能。 + +### 1. PIN 设备基础 + +- **硬件特性**:引脚可配置为输入(上拉 / 下拉)、输出(推挽 / 开漏),支持中断(上升沿 / 下降沿 / 电平触发)。 +- **在 I/O 模型中的定位**:属于**杂类设备**(`RT_Device_Class_Miscellaneous`),驱动层通过实现设备操作集,注册为 “pin” 设备。 + +### 2. PIN 设备核心接口 + +RT-Thread 提供封装后的 `rt_pin_xxx` 接口,简化 PIN 设备访问,底层对接 I/O 设备模型: + + + +| 功能 | 接口 | 底层实现逻辑 | +| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 获取引脚编号 | `rt_base_t rt_pin_get(const char* name)` | 解析 “PF.9” 等字符串,映射为驱动层定义的编号(如 35 对应 PB0) | +| 设置引脚模式 | `void rt_pin_mode(rt_base_t pin, rt_base_t mode)` | 调用设备 `control` 方法,配置引脚为输入 / 输出 / 上拉等 | +| 写电平 | `void rt_pin_write(rt_base_t pin, rt_base_t value)` | 调用设备 `control` 方法,设置输出寄存器(高 / 低电平) | +| 读电平 | `int rt_pin_read(rt_base_t pin)` | 调用设备 `control` 方法,读取输入寄存器状态 | +| 绑定中断 | `rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void*), void* args)` | 配置中断触发模式,注册回调函数到中断向量表 | +| 使能中断 | `rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled)` | 使能 / 禁用引脚中断(通过 NVIC 配置) | + +### 3. PIN 设备与 I/O 模型的对接实现 + +PIN 设备作为 I/O 设备的一种,其驱动层(如 `drv_gpio.c`)需实现 `struct rt_device_ops` 接口,并通过 `rt_device_register` 注册。 + +#### 对接关键:`rt_pin_xxx` 接口与设备操作集的映射 + +c + + + + + + + + + + + +```c +// PIN设备操作集(drv_gpio.c中实现) +static const struct rt_device_ops pin_ops = { + rt_pin_init, // 初始化(如使能GPIO时钟) + rt_pin_open, // 打开(引用计数+1) + rt_pin_close, // 关闭(引用计数-1) + RT_NULL, // 读(未直接实现,通过control间接操作) + RT_NULL, // 写(未直接实现,通过control间接操作) + rt_pin_control // 核心:处理模式设置、电平读写、中断配置 +}; + +// 注册PIN设备 +int rt_hw_pin_init(void) { + struct rt_device *dev = &pin_device; + dev->type = RT_Device_Class_Miscellaneous; + dev->ops = &pin_ops; + return rt_device_register(dev, "pin", 0); // 注册为"pin"设备 +} + +// rt_pin_mode底层调用control +void rt_pin_mode(rt_base_t pin, rt_base_t mode) { + rt_device_control(pin_dev, PIN_CMD_MODE, &mode_param); // 通过control传递命令 +} +``` + + + +- `rt_pin_control` 实现 + + :根据命令(如设置模式、读写电平)调用具体硬件操作: + + c + + + + + + + + + + + + ```c + static rt_err_t rt_pin_control(rt_device_t dev, int cmd, void* args) { + switch (cmd) { + case PIN_CMD_MODE: // 设置模式 + gpio_set_mode(pin, mode); // 配置GPIO寄存器(MODER/PUPDR) + break; + case PIN_CMD_WRITE: // 写电平 + gpio_set_level(pin, value); // 配置ODR/BSRR寄存器 + break; + // 其他命令:读电平、中断配置等 + } + } + ``` + +## 四、PIN 设备使用示例(按键控制蜂鸣器) + +通过示例理解 PIN 设备的访问流程,以及如何对接 I/O 模型: + +### 1. 硬件连接 + +- 蜂鸣器 → PB0(引脚编号 35,输出模式) +- 按键 0 → PD8(引脚编号 55,输入上拉,下降沿中断) +- 按键 1 → PD9(引脚编号 56,输入上拉,下降沿中断) + +### 2. 示例代码 + +c + + + + + + + + + + + +```c +#include +#include + +#define BEEP_PIN_NUM 35 // PB0 +#define KEY0_PIN_NUM 55 // PD8 +#define KEY1_PIN_NUM 56 // PD9 + +// 按键0中断回调:蜂鸣器开 +void beep_on(void *args) { + rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); // 写高电平,蜂鸣器响 +} + +// 按键1中断回调:蜂鸣器关 +void beep_off(void *args) { + rt_pin_write(BEEP_PIN_NUM, PIN_LOW); // 写低电平,蜂鸣器停 +} + +int pin_beep_sample(void) { + // 1. 初始化蜂鸣器引脚(输出模式,默认低电平) + rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); + rt_pin_write(BEEP_PIN_NUM, PIN_LOW); + + // 2. 初始化按键0(输入上拉,下降沿中断) + rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP); + rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL); + rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE); + + // 3. 初始化按键1(输入上拉,下降沿中断) + rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP); + rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL); + rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE); + + return 0; +} +MSH_CMD_EXPORT(pin_beep_sample, pin beep sample); +``` + +### 3. 对接流程解析 + +1. **驱动层**:`drv_gpio.c` 实现 `pin_ops` 操作集,注册 “pin” 设备; +2. **应用层**:通过 `rt_pin_mode`、`rt_pin_attach_irq` 等接口操作,底层转换为对 “pin” 设备的 `control` 调用; +3. **硬件交互**:`control` 方法根据命令配置 GPIO 寄存器(如 `MODER` 设为输出,`EXTI` 配置中断触发)。 + +## 五、总结 + +1. **I/O 设备模型核心**:通过三层架构和设备对象抽象,实现硬件与应用的解耦,统一访问接口; +2. **设备操作集**:驱动层的核心,定义了硬件操作的标准方法,是设备注册和访问的基础; +3. **PIN 设备对接**:作为杂类设备,通过实现 `rt_device_ops` 注册为 “pin” 设备,`rt_pin_xxx` 接口封装设备控制逻辑,简化应用开发; +4. **关键思想**:面向对象的抽象(设备继承内核对象)、面向接口编程(操作集定义标准),让硬件差异对应用透明。 + + + +通过掌握 I/O 设备模型,开发者可快速适配新硬件(只需实现驱动层操作集),应用程序无需修改即可复用。 \ No newline at end of file