diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222444151-1753107887293-1.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222444151-1753107887293-1.png" new file mode 100644 index 0000000000000000000000000000000000000000..3639af2cdcce838857e4a1da36251458f9740d67 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222444151-1753107887293-1.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222522237-1753107923443-3.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222522237-1753107923443-3.png" new file mode 100644 index 0000000000000000000000000000000000000000..6e81525f0629c83cde8e8f6e0ab7cd2b9f4a632a Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222522237-1753107923443-3.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222601531-1753107963922-5.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222601531-1753107963922-5.png" new file mode 100644 index 0000000000000000000000000000000000000000..4e1fb421c38e95781c2c2e97405fab97d17a3d26 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/image-20250721222601531-1753107963922-5.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213-1753107997847-8.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213-1753107997847-8.png" new file mode 100644 index 0000000000000000000000000000000000000000..ccbd8abad5cbcc65034e5055bcddcfba1d7b0434 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213-1753107997847-8.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..ccbd8abad5cbcc65034e5055bcddcfba1d7b0434 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" new file mode 100644 index 0000000000000000000000000000000000000000..74832e6bafcf56f6189556578e42baf3f42689c9 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" @@ -0,0 +1,15 @@ +# RTOS夏令营Day1作业 + +env环境搭建: +![image-20250721222444151](./assets/image-20250721222444151-1753107887293-1.png) + +qemu-vexpress-a9编译运行RTOS +![image-20250721222522237](./assets/image-20250721222522237-1753107923443-3.png) + +修改源码并编译 +![image-20250721222601531](./assets/image-20250721222601531-1753107963922-5.png) + +运行LVGL示例工程 +![LVGL](./assets/lgvs示例工程-1753107997847-8.png) + +我的笔记仓库:https://gitee.com/cytopsis/rsoc-rtt \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Event.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Event.c" new file mode 100644 index 0000000000000000000000000000000000000000..f157a6b78536ceaa7d698a74480cbfcc38b10940 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Event.c" @@ -0,0 +1,61 @@ +/* +事件演示: +两个线程,一个发送事件,一个接收事件。 +接收线程会等待事件FLAG3和FLAG5的到来,使用OR和AND两种方式。 +*/ +#include + +#define EVENT_FLAG3 (1 << 3) +#define EVENT_FLAG5 (1 << 5) + +static rt_event_t event = RT_NULL; +static rt_thread_t thread1 = RT_NULL; +static rt_thread_t thread2 = RT_NULL; + +void thread_recv(void *parameter) +{ + rt_uint32_t received_flags; + char* name = rt_thread_self()->name; + if(rt_event_recv(event, EVENT_FLAG3 | EVENT_FLAG5, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &received_flags) == RT_EOK) + { + rt_kprintf("%s: Event OR received: 0x%08x\n", name, received_flags); + } + rt_thread_mdelay(100); + if (rt_event_recv(event, EVENT_FLAG3 | EVENT_FLAG5, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &received_flags) == RT_EOK) + { + rt_kprintf("%s: Event AND received: 0x%08x\n", name, received_flags); + } +} + +void thread_send(void *parameter) +{ + char* name = rt_thread_self()->name; + rt_kprintf("%s: Event FLAG3 sent\n", name); + rt_event_send(event, EVENT_FLAG3); + + rt_thread_mdelay(10); // Simulate some work + rt_kprintf("%s: Event FLAG5 sent\n", name); + rt_event_send(event, EVENT_FLAG5); + + rt_thread_mdelay(10); // Simulate some work + rt_kprintf("%s: Event FLAG3 sent\n", name); + rt_event_send(event, EVENT_FLAG3); +} + +int main(void) +{ + event = rt_event_create("event", RT_IPC_FLAG_FIFO); + if (event == RT_NULL) + { + rt_kprintf("Event creation failed\n"); + return -1; + } + + thread1 = rt_thread_create("thread_recv", thread_recv, RT_NULL, 1024, 20, 10); + thread2 = rt_thread_create("thread_send", thread_send, RT_NULL, 1024, 21, 10); + + if (thread1 != RT_NULL) rt_thread_startup(thread1); + if (thread2 != RT_NULL) rt_thread_startup(thread2); + + return RT_EOK; +} diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Mailbox.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Mailbox.c" new file mode 100644 index 0000000000000000000000000000000000000000..7714c07387d576a3adab965874d902e3ce6c8af9 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Mailbox.c" @@ -0,0 +1,66 @@ +/* +消息邮箱演示: +两个线程,一个发送消息,一个接收消息。 +接收线程会等待消息的到来,直到收到特定的消息(message3)。 +*/ +#include + +static rt_mailbox_t mailbox = RT_NULL; +static rt_thread_t thread1 = RT_NULL; +static rt_thread_t thread2 = RT_NULL; + +static char message1[] = "Message1: Elo phy"; +static char message2[] = "Message2: Hello world"; +static char message3[] = "over"; +// message是char*,&message是char (*)[N] + +void thread_recv(void *parameter) +{ + char *name = rt_thread_self()->name; + char *msg; + + while (1) + { + if (rt_mailbox_recv(mailbox, (void **)&msg, RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("%s: Received message: %s\n", name, msg); + if(msg == message3)break; + } + } +} + +void thread_send(void *parameter) +{ + char *name = rt_thread_self()->name; + + rt_kprintf("%s: Sending message1\n", name); + rt_mailbox_send(mailbox, message1); + + rt_thread_mdelay(100); // Simulate some work + + rt_kprintf("%s: Sending message2\n", name); + rt_mailbox_send(mailbox, message2); + + rt_thread_mdelay(100); // Simulate some work + + rt_kprintf("%s: Sending message3\n", name); + rt_mailbox_send(mailbox, message3); +} + +int main(void) +{ + mailbox = rt_mailbox_create("mailbox", 10, RT_IPC_FLAG_PRIO); + if (mailbox == RT_NULL) + { + rt_kprintf("Mailbox creation failed\n"); + return -1; + } + + thread1 = rt_thread_create("thread_recv", thread_recv, RT_NULL, 1024, 20, 10); + thread2 = rt_thread_create("thread_send", thread_send, RT_NULL, 1024, 21, 10); + + if (thread1 != RT_NULL) rt_thread_startup(thread1); + if (thread2 != RT_NULL) rt_thread_startup(thread2); + + return RT_EOK; +} \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/MessageQue.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/MessageQue.c" new file mode 100644 index 0000000000000000000000000000000000000000..a20a3f9c0a116b66fce3227d236da39708ad00fc --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/MessageQue.c" @@ -0,0 +1,63 @@ +/* +消息队列演示: +两个线程,一个发送消息,一个接收消息。 +发送线程发送三个消息,其中第二个消息是紧急消息(插入队首),接受线程会先接受到这个消息。 +*/ +#include + +static rt_mq_t messagequeue = RT_NULL; +static rt_thread_t thread1 = RT_NULL; +static rt_thread_t thread2 = RT_NULL; + +char msg1[] = "Message1: Hello"; +char msg2[] = "Message2: World"; +char msg3[] = "over"; + +void thread_recv(void *parameter) +{ + char *name = rt_thread_self()->name; + char msg[32]; + + rt_thread_mdelay(200); // Simulate some work + while (1) + { + if (rt_mq_recv(messagequeue, msg, sizeof(msg), RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("%s: Received message: %s\n", name, msg); + if (strcmp(msg, "over") == 0) break; // Exit condition + } + } +} + +void thread_send(void *parameter) +{ + char *name = rt_thread_self()->name; + rt_mq_send(messagequeue, msg1, sizeof(msg1)); + rt_kprintf("%s: Sending message1\n", name); + + rt_mq_urgent(messagequeue, msg2, sizeof(msg2)); + rt_kprintf("%s: Sending urgent message2\n", name); + + rt_thread_mdelay(100); // Simulate some work + + rt_mq_send(messagequeue, msg3, sizeof(msg3)); + rt_kprintf("%s: Sending message3\n", name); +} + +int main(void) +{ + messagequeue = rt_mq_create("messagequeue", sizeof(char) * 32, 10, RT_IPC_FLAG_PRIO); + if (messagequeue == RT_NULL) + { + rt_kprintf("Message queue creation failed\n"); + return -1; + } + + thread1 = rt_thread_create("thread_recv", thread_recv, RT_NULL, 1024, 21, 10); + thread2 = rt_thread_create("thread_send", thread_send, RT_NULL, 1024, 20, 10); + + if (thread1 != RT_NULL) rt_thread_startup(thread1); + if (thread2 != RT_NULL) rt_thread_startup(thread2); + + return RT_EOK; +} diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Mutex.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Mutex.c" new file mode 100644 index 0000000000000000000000000000000000000000..ced413c42174537bbed781d79323b5679eeaf179 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Mutex.c" @@ -0,0 +1,58 @@ +/* +互斥锁演示: +三个线程对num1和num2进行自加,num1没有互斥锁保护,num2有互斥锁保护。 +按照道理来说,num1的值可能会出现错误,而num2的值应该是正确的。(我没有试出来) +*/ +#include + +static rt_mutex_t mutex = RT_NULL; +static rt_thread_t thread1 = RT_NULL; +static rt_thread_t thread2 = RT_NULL; +static rt_thread_t thread3 = RT_NULL; + +static rt_uint32_t num1, num2 = 0; + +void thread_entry(void *parameter) +{ + char* name = rt_thread_self()->name; + for(int i = 0; i < 100; i++) + { + num1++; // 没有互斥锁保护的变量 + } + for(int i = 0; i < 100; i++) + { + rt_mutex_take(mutex, RT_WAITING_FOREVER); + rt_kprintf("%s: Mutex taken\n", name); + num2++; // 互斥锁保护的变量 + rt_mutex_release(mutex); + } + +} + +int main(void) +{ + mutex = rt_mutex_create("mutex", RT_IPC_FLAG_PRIO); + if (mutex == RT_NULL) + { + rt_kprintf("Mutex creation failed\n"); + return -1; + } + + thread1 = rt_thread_create("thread1", thread_entry, RT_NULL, 1024, 21, 5); + thread2 = rt_thread_create("thread2", thread_entry, RT_NULL, 1024, 21, 5); + thread3 = rt_thread_create("thread3", thread_entry, RT_NULL, 1024, 21, 5); + + if (thread1 != RT_NULL) rt_thread_startup(thread1); + if (thread2 != RT_NULL) rt_thread_startup(thread2); + if (thread3 != RT_NULL) rt_thread_startup(thread3); + + // 等待线程结束 + rt_thread_delay(RT_TICK_PER_SECOND * 3); + + rt_kprintf("num1: %d, num2: %d\n", num1, num2); + + // 删除互斥锁 + rt_mutex_delete(mutex); + + return 0; +} diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Semaphore.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Semaphore.c" new file mode 100644 index 0000000000000000000000000000000000000000..6477cfef0cf285c103c0f386eaa7cfef25aa9dc3 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Semaphore.c" @@ -0,0 +1,44 @@ +/* +信号量演示: +信号量的初始值为2,三个线程尝试获取信号量,所以同时只有两个线程能够获取到信号量。 +创建信号量时选择了FIFO,但是实际运行时永远是线程1和3先获取到信号量,说明FIFO已被废弃。 +*/ +#include + +static rt_sem_t sem = RT_NULL; +static rt_thread_t thread1 = RT_NULL; +static rt_thread_t thread2 = RT_NULL; +static rt_thread_t thread3 = RT_NULL; + +void thread_entry(void *parameter) +{ + char* name = rt_thread_self()->name; + while (1) + { + rt_sem_take(sem, RT_WAITING_FOREVER); + rt_kprintf("%s: Semaphore taken\n", name); + rt_thread_mdelay(1000); // Simulate some work + rt_sem_release(sem); + rt_kprintf("%s: Semaphore released\n", name); + } +} + +int main(void) +{ + sem = rt_sem_create("sem", 2, RT_IPC_FLAG_FIFO); + if (sem == RT_NULL) + { + rt_kprintf("Semaphore creation failed\n"); + return -1; + } + + thread1 = rt_thread_create("thread1", thread_entry, RT_NULL, 1024, 21, 10); + thread2 = rt_thread_create("thread2", thread_entry, RT_NULL, 1024, 22, 10); + thread3 = rt_thread_create("thread3", thread_entry, RT_NULL, 1024, 21, 10); + + if (thread1 != RT_NULL) rt_thread_startup(thread1); + if (thread2 != RT_NULL) rt_thread_startup(thread2); + if (thread3 != RT_NULL) rt_thread_startup(thread3); + + return 0; +} \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Signal.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Signal.c" new file mode 100644 index 0000000000000000000000000000000000000000..7f4cf2a8808917ba9930b4206ad8652aabdd0e00 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/Signal.c" @@ -0,0 +1,50 @@ +/* +信号演示: +一个线程注册了一个信号处理函数,当接收到SIGUSR1信号时,会打印出线程名称和信号编号。 +主函数创建了这个线程,并在一段时间后向它发送SIGUSR1信号。 +*/ +#include + +static rt_thread_t thread1 = RT_NULL; + +void thread_signal_handler(int sig) +{ + char *name = rt_thread_self()->name; + rt_kprintf("%s: Signal %d received\n", name, sig); +} + +void thread_entry(void *parameter) +{ + char *name = rt_thread_self()->name; + + // Register signal handler + rt_signal_install(SIGUSR1, thread_signal_handler); + rt_signal_unmask(SIGUSR1); // Unmask the signal to allow it to be received, well this is very important + + // Simulate some work + for (int cnt = 0; cnt < 10; cnt++) + { + rt_kprintf("%s count : %d\n",name,cnt); + rt_thread_mdelay(100); + } +} + +int main(void) +{ + thread1 = rt_thread_create("thread1", thread_entry, RT_NULL, 1024, 21, 10); + if (thread1 != RT_NULL) + { + rt_thread_startup(thread1); + } + else + { + rt_kprintf("Thread creation failed\n"); + return -1; + } + + // Simulate sending a signal to the thread + rt_thread_mdelay(500); // Wait for a while before sending the signal + rt_thread_kill(thread1, SIGUSR1); + + return 0; +} diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.c" new file mode 100644 index 0000000000000000000000000000000000000000..997c89b103fc03f40857f0c3984a2044233bb5e7 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.c" @@ -0,0 +1,32 @@ +#include +#include +#include + +rt_thread_t tid = RT_NULL; +rt_thread_t tid2 = RT_NULL; +rt_thread_t tid3 = RT_NULL; +void my_thread() { + char* name; + while(1) { + name = rt_thread_self()->name; + rt_kprintf("%s: Hello!\r\n",name); +// rt_thread_delay(1000); + } +} + +int main(void) +{ + // 线程1和线程2优先级轮转,线程3被抢占不会执行 + tid = rt_thread_create("LittlePipu", my_thread, RT_NULL, 1024, 21, 20); + tid2 = rt_thread_create("Cytilopsis", my_thread, RT_NULL, 1024, 21, 20); + tid3 = rt_thread_create("Angelica", my_thread, RT_NULL, 1024, 22, 20); + + if(tid && tid2 && tid3 != RT_NULL) { + rt_thread_startup(tid); + rt_thread_startup(tid2); + rt_thread_startup(tid3); + } + + rt_kprintf("Main: Hello from main!\r\n"); + return RT_EOK; +} diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/main.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/main.c" new file mode 100644 index 0000000000000000000000000000000000000000..d2f3564ccf7b1985c8463765e5225d73d1316731 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232/main.c" @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2006-2020, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2020/12/31 Bernard Add license info + */ + +#include +#include +#include +#include +#include +#include "mqttclient.h" +#include + +#ifndef MY_NAME +#define MY_NAME "LittlePipu!" +#endif +#ifndef MY_STUDY +#define MY_STUDY "MQTT" +#endif +#ifndef KAWAII_MQTT_HOST +#define KAWAII_MQTT_HOST "jiejie01.top" +#endif +#ifndef KAWAII_MQTT_PORT +#define KAWAII_MQTT_PORT "1883" +#endif +#ifndef KAWAII_MQTT_CLIENTID +#define KAWAII_MQTT_CLIENTID "rtthread001" +#endif +#ifndef KAWAII_MQTT_USERNAME +#define KAWAII_MQTT_USERNAME "rt-thread" +#endif +#ifndef KAWAII_MQTT_PASSWORD +#define KAWAII_MQTT_PASSWORD "rt-thread" +#endif +#ifndef KAWAII_MQTT_SUBTOPIC +#define KAWAII_MQTT_SUBTOPIC "rtt-sub" +#endif +#ifndef KAWAII_MQTT_PUBTOPIC +#define KAWAII_MQTT_PUBTOPIC "rtt-pub" +#endif + +mqtt_client_t *client = NULL; + +static void subscribe_handle(void* client, message_data_t* msg) +{ + (void) client; + KAWAII_MQTT_LOG_I("-----------------------------------------------------------------------------------"); + KAWAII_MQTT_LOG_I("%s:%d %s()...\ntopic: %s\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload); + KAWAII_MQTT_LOG_I("-----------------------------------------------------------------------------------"); +} + + +static int publish_handle(mqtt_client_t *client) +{ + mqtt_message_t msg; + memset(&msg, 0, sizeof(msg)); + char buffer[128]; + sprintf(buffer, "{ \"name\":\"%s\",\"study\":\"%s\" }", MY_NAME, MY_STUDY); + struct cJSON* root = cJSON_Parse(buffer); + char *payload = cJSON_Print(root); + // rt_kprintf("Publishing message: %s\n", payload); + msg.qos = QOS0; + msg.payload = (void *)payload; + cJSON_Delete(root); + return mqtt_publish(client, KAWAII_MQTT_PUBTOPIC, &msg); +} + +int main(void) +{ + rt_kprintf("Hello RT-Thread!\n"); + rt_kprintf("LittlePipu: Hello RT-Thread!\n"); + rt_thread_delay(6000); + + mqtt_log_init(); + + client = mqtt_lease(); + + mqtt_set_host(client, KAWAII_MQTT_HOST); + mqtt_set_port(client, KAWAII_MQTT_PORT); + mqtt_set_user_name(client, KAWAII_MQTT_USERNAME); + mqtt_set_password(client, KAWAII_MQTT_PASSWORD); + mqtt_set_client_id(client, KAWAII_MQTT_CLIENTID); + mqtt_set_clean_session(client, 1); + + KAWAII_MQTT_LOG_I("The ID of the Kawaii client is: %s ", KAWAII_MQTT_CLIENTID); + + mqtt_connect(client); + + mqtt_subscribe(client, KAWAII_MQTT_SUBTOPIC, QOS0, subscribe_handle); + + KAWAII_MQTT_LOG_I("LittlePipu: MQTT client subscribed to topic: %s\n", KAWAII_MQTT_SUBTOPIC); + + while (1) { + publish_handle(client); + mqtt_sleep_ms(5000); + } + + return 0; +} diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv_vir.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv_vir.c" new file mode 100644 index 0000000000000000000000000000000000000000..dee5b95614dab628a2006af6a19e3aaffffcef7c --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv_vir.c" @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * 设备驱动层 + * Change Logs: + * Date Author Notes + * 2025-07-24 Cytilopsis the first version + */ +#include + +struct vir +{ + struct rt_vir_device parent; + rt_uint32_t val; + char* info; +}; + +#if defined(RT_USING_VIR) + +struct vir vir; +void print_info(struct rt_device* device) { + struct vir* dev = (struct vir*)device; + if(dev->info)rt_kprintf("VIR Info: %s\n",dev->info); +} +void vir_set_val(struct rt_device* device, rt_uint32_t val) { + struct vir* dev = (struct vir*)device; + if(dev)dev->val = val; +} +void vir_get_val(struct rt_device* device, rt_uint32_t* val) { + struct vir* dev = (struct vir*)device; + if(dev)*val = dev->val; +} +struct rt_vir_ops ops = { + print_info, + vir_set_val, + vir_get_val +}; +static int vir_init() { + vir.val = 0; + vir.info = "小派普!"; + rt_hw_vir_register(&vir.parent, "VIRPipu!", &ops, &vir.info); + return 0; +} +INIT_APP_EXPORT(vir_init); + +#endif diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/pin\350\256\276\345\244\207\345\257\271\346\216\245\345\210\206\346\236\220.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/pin\350\256\276\345\244\207\345\257\271\346\216\245\345\210\206\346\236\220.md" new file mode 100644 index 0000000000000000000000000000000000000000..629a26d8013404122b32911746394bb8b48835e8 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/pin\350\256\276\345\244\207\345\257\271\346\216\245\345\210\206\346\236\220.md" @@ -0,0 +1,47 @@ +## PIN设备对接 + +#### 将PIN设备封装成标准设备 + +这部分工作目的是让PIN设备能够被**I/O设备管理层**识别和管理。 + +* **定义标准设备接口 (`_pin_read`, `_pin_write`, `_pin_control`)** + * `pin.c`中定义了`_pin_read`, `_pin_write`, `_pin_control`这三个静态函数。 + * 这三个函数遵循了I/O设备管理层`rt_device`的标准接口格式(`read`, `write`, `control`)。 + * 它们的作用是作为“翻译官”,将上层对标准设备的通用调用(如`rt_device_read`)转换成对具体PIN设备的操作。例如,`_pin_write`函数会将收到的数据解析为引脚号(pin)和电平值(status),然后调用底层的`pin->ops->pin_write`。 + +* **创建标准设备实例 (`_hw_pin`)** + * 代码中定义了一个全局静态变量 `static struct rt_device_pin _hw_pin;`。 + * 这个`_hw_pin`就是PIN设备在系统中的唯一实例。它继承`rt_device`父类成员,使其能够被I/O设备管理层识别。 + +* **注册到I/O设备管理层 (`rt_device_pin_register`)** + * 这个函数是负责做一些初始化并向上层IO设备管理层注册一个PIN设备。 + * 它将`_pin_read`, `_pin_write`, `_pin_control`的函数指针赋值给`_hw_pin.parent`的`read`, `write`, `control`成员。 + * 最后调用`rt_device_register(&_hw_pin.parent, name, ...)`,将这个封装好的设备注册到系统中,使其成为一个可被`rt_device_find`找到的标准设备。 + +#### 与底层具体驱动的连接 + +在`pin.c`定义了一套底层PIN设备都有的功能在`struct rt_pin_ops`中。 + +* **定义底层驱动操作集 (`struct rt_pin_ops`)** + * `struct rt_pin_ops`结构体包含了一组函数指针,`pin_mode`, `pin_write`, `pin_read`, `pin_attach_irq`等。 + * 这套操作集定义了所有具体的GPIO驱动(如STM32的GPIO驱动)必须实现的功能。 + +* **连接底层驱动 (`rt_device_pin_register`)** + * `rt_device_pin_register`函数接收一个`const struct rt_pin_ops *ops`参数。 + * `_hw_pin.ops = ops;`将由**设备驱动层**(比如说`drv_gpio.c`)实现的具体硬件操作函数集,与PIN设备框架关联起来。 + +* **调用链** + 当上层应用调用`rt_pin_write(pin_num, level)`时: + 1. `rt_pin_write()` -> 调用`_hw_pin.ops->pin_write()` + 2. `_hw_pin.ops`指针指向的是**设备驱动层**(例如`drv_gpio.c`)中实现的、针对具体芯片的GPIO写函数。 + 3. 最终,调用会落到`drv_gpio.c`中(`stm32_pin_write()`函数),然后通过调用**SDK**(HAL库`HAL_GPIO_WritePin()`)来操作硬件。 + +#### 映射关系 + +* **应用层**:调用`rt_pin_mode()`, `rt_pin_read()`, `rt_pin_write()`等API。 +* **I/O设备管理层**:管理名为`"pin"`的`rt_device`设备。上层也可以通过`rt_device_find("pin")`后,使用`rt_device_write()`等标准接口操作,最终会调用到`_pin_write`。 +* **设备驱动框架层**:`pin.c`文件本身。它定义了`struct rt_pin_ops`,并实现了`_pin_read/write/control`等粘合函数。 +* **设备驱动层**:一个具体的`drv_gpio.c`文件(例如在`bsp/stm32/drivers/drv_gpio.c`)。它会: + 1. 定义一个`rt_pin_ops`结构体的实例,并用操作具体GPIO的函数(如`stm32_pin_write`)来填充它。 + 2. 在自己的初始化函数(如`rt_hw_pin_init`)中,调用`rt_device_pin_register("pin", &stm32_pin_ops, RT_NULL);`来完成对接。 +* **SDK/硬件**:`drv_gpio.c`中的函数会调用HAL库/LL库等SDK函数,最终操作GPIO寄存器。 diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/vir.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/vir.c" new file mode 100644 index 0000000000000000000000000000000000000000..d9b685aee6c081718ac3c725a6aeed67ceb13a2a --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/vir.c" @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * 驱动框架层 + * Change Logs: + * Date Author Notes + * 2025-07-24 Cytilopsis the first version + */ +#include +#include + +#if defined(RT_USING_VIR) + +rt_err_t _vir_init(rt_device_t dev) { + rt_kprintf("LittlePipu: Virtual Device Initialized!\n"); + return RT_EOK; +} +rt_err_t _vir_open(rt_device_t dev, rt_uint16_t oflag) { + rt_kprintf("LittlePipu: Hello From Virtual Device!\n"); + return RT_EOK; +} +rt_err_t _vir_close(rt_device_t dev){ + rt_kprintf("LittlePipu: Virtual Device is closed!\n"); + return RT_EOK; +} +rt_size_t _vir_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_get_val(dev, (rt_uint32_t*)buffer); + return 4; + } + return 0; +} +rt_size_t _vir_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_set_val(dev, *(rt_uint32_t*)buffer); + return 4; + } + return 0; +} +rt_err_t _vir_control(rt_device_t dev, int cmd, void *args) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + rt_kprintf("LittlePipu: Virtual Device received cmd %02d\n",cmd); + return RT_EOK; + } + return -RT_ERROR; +} + + +rt_err_t rt_hw_vir_register(rt_vir_device_t device, const char* name, const struct rt_vir_ops* ops, const void* user_data) { + RT_ASSERT(ops != RT_NULL); + rt_err_t result; + + device->ops = ops; + device->parent.init = _vir_init; + device->parent.open = _vir_open; + device->parent.close = _vir_close; + device->parent.read = _vir_read; + device->parent.write = _vir_write; + device->parent.control = _vir_control; + + result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR); + return result; +} + +// 驱动框架层特殊API +rt_err_t rt_vir_read(rt_device_t dev, rt_uint32_t* val) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_get_val(dev, (rt_uint32_t*)val); + return RT_EOK; + } + return -RT_ERROR; +} +rt_err_t rt_vir_write(rt_device_t dev, rt_uint32_t val) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_set_val(dev, val); + return RT_EOK; + } + return -RT_ERROR; +} + +#endif diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/vir.h" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/vir.h" new file mode 100644 index 0000000000000000000000000000000000000000..703d1350707f1313f3f10c53d01f3e4d493b5810 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/vir.h" @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2025-07-24 Cytilopsis the first version + */ +#ifndef __VIR_H_ +#define __VIR_H_ + +#include + +struct rt_vir_ops { + void (*print_info) (struct rt_device* device); + void (*vir_set_val) (struct rt_device* device, rt_uint32_t val); + void (*vir_get_val) (struct rt_device* device, rt_uint32_t* val); +}; + +struct rt_vir_device { + struct rt_device parent; + const struct rt_vir_ops *ops; +}; +typedef struct rt_vir_device* rt_vir_device_t; + +rt_err_t rt_vir_read(rt_device_t dev, rt_uint32_t* val); +rt_err_t rt_vir_write(rt_device_t dev, rt_uint32_t val); + +#endif diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/Git\347\220\206\350\247\243.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/Git\347\220\206\350\247\243.md" new file mode 100644 index 0000000000000000000000000000000000000000..48d0e61fa466da220c0ba5844670f45aa3c4ac78 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/Git\347\220\206\350\247\243.md" @@ -0,0 +1,54 @@ +# 深入理解Git + +> 参考文章:https://missing.csail.mit.edu/2020/version-control/ + +![xkcd 1597](./assets/git.png) + +## Git 命令 + +更多信息请参考强烈推荐的《Pro Git》或观看讲座视频。 + +### 基础操作 +- `git help `:获取 Git 命令的帮助信息 +- `git init`:创建新的 Git 仓库,数据存储在 `.git` 目录 +- `git status`:显示当前状态信息 +- `git add `:将文件添加到暂存区 +- `git commit`:创建新提交 +- `git log`:显示扁平化的历史日志 +- `git log --all --graph --decorate`:以有向无环图(DAG)形式可视化历史 +- `git diff `:显示相对于暂存区的文件更改 +- `git diff `:显示文件在两个快照版本间的差异 +- `git checkout `:更新 HEAD(若检出分支则同时更新当前分支) + +### 分支与合并 +- `git branch`:显示分支列表 +- `git branch `:创建新分支 +- `git checkout -b `:创建分支并切换至该分支 + - (等同于 `git branch `; `git checkout `) +- `git merge `:将指定修订合并到当前分支 +- `git mergetool`:使用高级工具解决合并冲突 +- `git rebase`:将补丁集重新基于新基准 + +### 远程操作 +- `git remote`:列出远程仓库 +- `git remote add `:添加远程仓库 +- `git push :`:推送对象至远程并更新远程引用 +- `git branch --set-upstream-to=/`:设置本地分支与远程分支的对应关系 +- `git fetch`:从远程仓库获取对象/引用 +- `git pull`:等同于 `git fetch` + `git merge` +- `git clone`:从远程仓库下载代码库 + +### 撤销操作 +- `git commit --amend`:编辑提交内容/消息 +- `git reset HEAD `:取消文件的暂存状态 +- `git checkout -- `:丢弃文件更改 + +### 高级 Git +- `git config`:Git 具有高度可配置性 +- `git clone --depth=1`:浅克隆(不包含完整版本历史) +- `git add -p`:交互式暂存 +- `git rebase -i`:交互式重基 +- `git blame`:显示最后编辑某行的责任人 +- `git stash`:临时移除工作目录的修改 +- `git bisect`:二进制搜索历史(用于定位特定修订) +- `.gitignore`:指定需忽略的文件/模式 diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/IPC.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/IPC.md" new file mode 100644 index 0000000000000000000000000000000000000000..23cf88be3877a5bdf6223b41562e670ff9305466 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/IPC.md" @@ -0,0 +1,1498 @@ +# RT-Thread IPC + +## 一、信号量(Semaphore) + +计数器+等待队列的同步机制,用于协调多线程对共享资源的访问,可以用来表示共享资源的数量。 + +- `release`(V操作):信号量值+1,唤醒等待队列中的线程 +- `take`(P操作):尝试获取信号量(值>0时减1立即返回,否则阻塞等待) + +信号量结构体如下: +```c +struct rt_semaphore +{ + struct rt_ipc_object parent; /**< inherit from ipc_object */ + + rt_uint16_t value; /**< value of semaphore. */ + rt_uint16_t reserved; /**< reserved field */ +}; +struct rt_ipc_object +{ + struct rt_object parent; /**< inherit from rt_object */ + + rt_list_t suspend_thread; /**< threads pended on this resource */ +}; +struct rt_object +{ + char name[RT_NAME_MAX]; /**< name of kernel object */ + rt_uint8_t type; /**< type of kernel object */ + rt_uint8_t flag; /**< flag of kernel object */ + +#ifdef RT_USING_MODULE + void *module_id; /**< id of application module */ +#endif + rt_list_t list; /**< list node of kernel object */ +}; +``` + +### 示例代码 + +```c +/* + * 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 + */ + +/* + * 程序清单:信号量例程 + * + * 该例程创建了一个动态信号量,初始化两个线程,线程1在count每计数10次时, + * 发送一个信号量,线程2在接收信号量后,对number进行加1操作 + */ +#include + +#define THREAD_PRIORITY 25 +#define THREAD_TIMESLICE 5 + +/* 指向信号量的指针 */ +static rt_sem_t dynamic_sem = RT_NULL; + +#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_thread1_entry(void *parameter) +{ + static rt_uint8_t count = 0; + + while (1) + { + if (count <= 100) + { + count++; + } + else + return; + + /* count每计数10次,就释放一次信号量 */ + if (0 == (count % 10)) + { + rt_kprintf("thread1 release a dynamic semaphore.\n"); + rt_sem_release(dynamic_sem); + } + } +} + +#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_thread2_entry(void *parameter) +{ + static rt_err_t result; + static rt_uint8_t number = 0; + while (1) + { + /* 永久方式等待信号量,获取到信号量,则执行number自加的操作 */ + result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER); + if (result != RT_EOK) + { + rt_kprintf("thread2 take a dynamic semaphore, failed.\n"); + rt_sem_delete(dynamic_sem); + return; + } + else + { + number++; + rt_kprintf("thread2 take a dynamic semaphore. number = %d\n", number); + } + } +} + +/* 信号量示例的初始化 */ +int semaphore_sample() +{ + /* 创建一个动态信号量,初始值是0 */ + dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO); + if (dynamic_sem == RT_NULL) + { + rt_kprintf("create dynamic semaphore failed.\n"); + return -1; + } + else + { + rt_kprintf("create done. dynamic semaphore value = 0.\n"); + } + + 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); + + 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); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(semaphore_sample, semaphore sample); + +``` + +#### 1. 信号量创建 +```c +dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO); +``` +- 初始值=0(无可用资源) +- `RT_IPC_FLAG_PRIO`:唤醒策略为优先级调度,**FIFO已被废弃,不管填什么都是优先级调度** +- **RT-Thread中信号量和线程一样有两种创建方式,静态(init)和动态(create),前者是编译时分配空间,使用detach释放,后者是运行时分配,使用delete释放。** + +#### 2. thread1 +```c +void rt_thread1_entry(void *parameter) { + while(count++ <= 100) { + if(count % 10 == 0) { // 每10次计数触发 + rt_sem_release(dynamic_sem); // V操作 + rt_kprintf("Release semaphore!\n"); + } + } +} +``` +- 周期性释放信号量(每次release一个资源) + +#### 3. thread2 +```c +void rt_thread2_entry(void *parameter) { + while(1) { + rt_sem_take(dynamic_sem, RT_WAITING_FOREVER); // P操作 + number++; + rt_kprintf("Take semaphore. number=%d\n", number); + } +} +``` +- `RT_WAITING_FOREVER`参数使线程持续阻塞直到信号量可用 +- 当thread1释放信号量后立即被唤醒执行 + +### 执行流程 +```mermaid +sequenceDiagram + participant T1 as Thread1(生产者) + participant Sem as 信号量(dsem) + participant T2 as Thread2(消费者) + + T1->>Sem: rt_sem_release() [count=10] + Sem->>T2: 唤醒等待线程 + T2->>Sem: rt_sem_take()成功 + T2->>T2: number++ (输出1) + + T1->>Sem: rt_sem_release() [count=20] + Sem->>T2: 再次唤醒 + T2->>Sem: rt_sem_take()成功 + T2->>T2: number++ (输出2) +``` + +- 当thread2执行take时,若信号量值=0则主动进入阻塞态 +- thread1的release操作将thread2移入就绪队列 +- 高优先级线程(thread2)优先获取信号量 + +### 示例输出 +``` +create done. dynamic semaphore value = 0. +thread1 release a dynamic semaphore. +thread2 take a dynamic semaphore. number = 1 +thread1 release a dynamic semaphore. +thread2 take a dynamic semaphore. number = 2 +...(每10次计数重复)... +``` + +## 二、互斥量 + +互斥量(Mutex)用于保护共享资源免受并发冲突。其核心特性是**互斥访问**,当一个线程持有互斥量时,其他线程无法同时持有,必须等待释放。 + +互斥量的结构体如下: + +```c +struct rt_mutex +{ + struct rt_ipc_object parent; /**< inherit from ipc_object */ + + rt_uint16_t value; /**< value of mutex */ + + rt_uint8_t original_priority; /**< priority of last thread hold the mutex */ + rt_uint8_t hold; /**< numbers of thread hold the mutex */ + + struct rt_thread *owner; /**< current owner of mutex */ +}; +``` + +**互斥量有一个owner,只有持有者能够对其unlock,其他线程进行unlock会产生未定义行为** + +### 示例代码 + +```c +/* + * 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 + */ + +/* + * 程序清单:互斥锁例程 + * + * 互斥锁是一种保护共享资源的方法。当一个线程拥有互斥锁的时候, + * 可以保护共享资源不被其他线程破坏。线程1对2个number分别进行加1操作 + * 线程2也会对2个number分别进行加1操作。使用互斥量保证2个number值保持一致 + */ +#include + +#define THREAD_PRIORITY 8 +#define THREAD_TIMESLICE 5 + +/* 指向互斥量的指针 */ +static rt_mutex_t dynamic_mutex = RT_NULL; +static rt_uint8_t number1, number2 = 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; +static void rt_thread_entry1(void *parameter) +{ + while (1) + { + /* 线程1获取到互斥量后,先后对number1、number2进行加1操作,然后释放互斥量 */ + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + number1++; + rt_thread_mdelay(10); + number2++; + rt_mutex_release(dynamic_mutex); + } +} + +#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) +{ + while (1) + { + /* 线程2获取到互斥量后,检查number1、number2的值是否相同,相同则表示mutex起到了锁的作用 */ + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + if (number1 != number2) + { + rt_kprintf("not protect.number1 = %d, mumber2 = %d \n", number1, number2); + } + else + { + rt_kprintf("mutex protect ,number1 = mumber2 is %d\n", number1); + } + + number1++; + number2++; + rt_mutex_release(dynamic_mutex); + + if (number1 >= 50) + return; + } +} + +/* 互斥量示例的初始化 */ +int mutex_sample(void) +{ + /* 创建一个动态互斥量 */ + dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO); + if (dynamic_mutex == RT_NULL) + { + rt_kprintf("create dynamic mutex failed.\n"); + return -1; + } + + 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); + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mutex_sample, mutex sample); + +``` + +#### 1. 互斥量创建 +```c +dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO); +``` +- 动态创建 + +#### 2. 线程1操作 +```c +rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); // 获取锁 +number1++; +rt_thread_mdelay(10); +number2++; +rt_mutex_release(dynamic_mutex); // 释放锁 +``` +- 通过互斥量保护 `number1++` 和 `number2++` 的原子性 +- 即使线程1在操作中被延时(`rt_thread_mdelay`)让出CPU,线程2也无法进入临界区。 + +#### 3. 线程2操作 +```c +rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); +if (number1 != number2) { // 检查数据一致性 + rt_kprintf("not protect! number1=%d, number2=%d\n", number1, number2); +} else { + rt_kprintf("mutex protect success! value=%d\n", number1); +} +number1++; number2++; // 验证后修改 +rt_mutex_release(dynamic_mutex); +``` +- 验证互斥量对关联操作的完整性保护 + +### 执行流程 +```mermaid +sequenceDiagram + thread2->>+Mutex: 请求获取锁 + Mutex-->>-thread2: 授予锁 + thread2->>共享变量: 检查/修改 number1,number2 + thread2->>Mutex: 释放锁 + thread1->>+Mutex: 请求获取锁 + Mutex-->>-thread1: 授予锁 + thread1->>共享变量: 修改number1 + thread1->>延时: 10ms延迟 + thread1->>共享变量: 修改number2 + thread1->>Mutex: 释放锁 +``` + +### 示例输出 + +``` +mutex protect ,number1 = mumber2 is 1 +mutex protect ,number1 = mumber2 is 2 +mutex protect ,number1 = mumber2 is 3 +mutex protect ,number1 = mumber2 is 4 +mutex protect ,number1 = mumber2 is 5 +mutex protect ,number1 = mumber2 is 6 +mutex protect ,number1 = mumber2 is 7 +mutex protect ,number1 = mumber2 is 8 +mutex protect ,number1 = mumber2 is 9 +mutex protect ,number1 = mumber2 is 10 +...(一直到49) +``` + +## 三、事件 + +事件允许线程等待一组特定事件标志(flag)的组合。每个事件标志是一个位(bit),用 32 位整数的不同位表示不同事件。 + +- 支持"逻辑或"(任一事件发生即触发)和"逻辑与"(所有事件同时发生才触发)两种模式 +- 仅用于状态通知,不携带数据 +- 事件发生后会持续存在直到被清除 +- 单个事件可被多个线程等待 + +事件的结构体如下: + +```c +struct rt_event +{ + struct rt_ipc_object parent; /**< inherit from ipc_object */ + + rt_uint32_t set; /**< event set */ +}; +``` + +### 示例代码 + +```c +/* + * 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 + */ + +/* + * 程序清单:事件例程 + * + * 程序会初始化2个线程及初始化一个静态事件对象 + * 一个线程等待于事件对象上,以接收事件; + * 一个线程发送事件 (事件3/事件5) +*/ +#include + +#define THREAD_PRIORITY 9 +#define THREAD_TIMESLICE 5 + +#define EVENT_FLAG3 (1 << 3) +#define EVENT_FLAG5 (1 << 5) + +/* 事件控制块 */ +static struct rt_event event; + +#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_recv_event(void *param) +{ + rt_uint32_t e; + + /* 第一次接收事件,事件3或事件5任意一个可以触发线程1,接收完后清除事件标志 */ + 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); + + /* 第二次接收事件,事件3和事件5均发生时才可以触发线程1,接收完后清除事件标志 */ + 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_kprintf("thread1 leave.\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_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); +#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", + thread2_send_event, + 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(event_sample, event sample); + +``` + +#### 1. 事件标志定义 +```c +#define EVENT_FLAG3 (1 << 3) // 位3(二进制: 00001000) +#define EVENT_FLAG5 (1 << 5) // 位5(二进制: 00100000) +``` +- 使用位操作定义事件标志,每个标志独占一个 bit 位 + +#### 2. 事件对象静态初始化 +```c +result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO); +``` +- `&event`:事件控制块,使用动态创建不需要这个 +- `"event"`:事件名称 +- `RT_IPC_FLAG_PRIO`:优先级等待模式 + +#### 3. 线程1 +```c +// OR 模式接收(任一事件即可触发) +rt_event_recv(&event, + (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, + &e); + +// AND 模式接收(必须同时满足) +rt_event_recv(&event, + (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, + &e); +``` +- `RT_EVENT_FLAG_OR`:逻辑或(任何标志置位即触发) +- `RT_EVENT_FLAG_AND`:逻辑与(所有标志同时置位才触发) + +- `RT_EVENT_FLAG_CLEAR`:接收后自动清除事件标志 +- `RT_WAITING_FOREVER`:永久阻塞等待 + +#### 4. 线程2 +```c +rt_event_send(&event, EVENT_FLAG3); // 发送事件3 +rt_event_send(&event, EVENT_FLAG5); // 发送事件5 +``` +- 多个事件可独立或组合发送 +- 发送后会自动唤醒符合条件的等待线程 + +--- + +### 执行流程 +```mermaid +sequenceDiagram + participant Main as 主线程 + participant Thread1 as 线程1(接收) + participant Thread2 as 线程2(发送) + participant Event as 事件对象 + + Main->>Event: rt_event_init() + Main->>Thread1: 创建并启动 + Main->>Thread2: 创建并启动 + + Thread1->>Event: 等待OR模式(事件3或5) + Thread2->>Event: 发送事件3 + Event->>Thread1: 满足OR条件,唤醒 + Thread1->>Event: 等待AND模式(事件3和5) + Thread2->>Event: 发送事件5 + Thread2->>Event: 发送事件3 + Event->>Thread1: 满足AND条件(已有3+5),唤醒 +``` + +- 事件发送后标志位会保持置位状态 +- 例:第一次发送`EVENT_FLAG3`后,该标志保持为1,直到被清除 +- 清除方式: + - 自动清除:使用`RT_EVENT_FLAG_CLEAR` + - 手动清除:`rt_event_control()`设置清除 + +- 可设置等待超时:`rt_event_recv(..., rt_int32_t timeout)` +- 特殊值: + - `RT_WAITING_FOREVER` = -1 // 永久等待 + - `RT_WAITING_NO` = 0 // 立即返回 + +### 示例输出 +``` +thread2: send event3 +thread1: OR recv event 0x8 // 收到事件3(8=2^3) +thread1: delay 1s to prepare the second event +thread2: send event5 +thread2: send event3 +thread2 leave. +thread1: AND recv event 0x28 // 28=32(2^5)+8(2^3) +thread1 leave. +``` + +### 事件、信号量、互斥量的对比 +| 特性 | 事件(Event) | 信号量(Semaphore) | 互斥量(Mutex) | +|---------------|---------------------|-------------------|-----------------| +| 同步类型 | 条件同步 | 计数同步 | 互斥访问 | +| 事件组合 | 支持 AND/OR | 不支持 | 不支持 | +| 数据传递 | 无 | 无 | 无 | +| 优先级继承 | 不支持 | 不支持 | 支持 | +| 适用场景 | 多条件等待 | 资源计数 | 临界区保护 | + +## 四、消息邮箱 + +RT-Thread的邮箱是长度固定(4字节)的队列,用于传递指针或整数值(例程中传递字符串地址) + +- 提供阻塞/非阻塞两种访问模式 + +消息邮箱的结构体如下: +```c +struct rt_mailbox +{ + struct rt_ipc_object parent; /**< inherit from ipc_object */ + + rt_ubase_t *msg_pool; /**< start address of message buffer */ + + rt_uint16_t size; /**< size of message pool */ + + rt_uint16_t entry; /**< index of messages in msg_pool */ + rt_uint16_t in_offset; /**< input offset of the message buffer */ + rt_uint16_t out_offset; /**< output offset of the message buffer */ + + rt_list_t suspend_sender_thread; /**< sender thread suspended on this mailbox */ +}; +``` + +### 示例代码 + +```c +/* + * 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 + */ + +/* + * 程序清单:邮箱例程 + * + * 这个程序会创建2个动态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件, + * 一个线程往邮箱中收取邮件。 + */ +#include + +#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"; + +#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) +{ + char *str; + + while (1) + { + rt_kprintf("thread1: try to recv a mail\n"); + + /* 从邮箱中收取邮件 */ + if (rt_mb_recv(&mb, (rt_ubase_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; + + /* 延时100ms */ + rt_thread_mdelay(100); + } + } + /* 执行邮箱对象脱离 */ + rt_mb_detach(&mb); +} + +#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_uint8_t count; + + count = 0; + while (count < 10) + { + count ++; + if (count & 0x1) + { + /* 发送mb_str1地址到邮箱中 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str1); + } + else + { + /* 发送mb_str2地址到邮箱中 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str2); + } + + /* 延时200ms */ + rt_thread_mdelay(200); + } + + /* 发送邮件告诉线程1,线程2已经运行结束 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str3); +} + +int mailbox_sample(void) +{ + rt_err_t result; + + /* 初始化一个mailbox */ + result = rt_mb_init(&mb, + "mbt", /* 名称是mbt */ + &mb_pool[0], /* 邮箱用到的内存池是mb_pool */ + sizeof(mb_pool) / sizeof(rt_ubase_t), /* 邮箱中的邮件数目,sizeof(rt_ubase_t)表示指针大小 */ + RT_IPC_FLAG_PRIO); /* 采用PRIO方式进行线程等待 */ + 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); +#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", + 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); + +``` + +#### 1. 邮箱初始化 +```c +rt_mb_init(&mb, // 邮箱控制块 + "mbt", // 邮箱名称 + mb_pool, // 内存池地址(存储邮件) + sizeof(mb_pool)/sizeof(rt_ubase_t), // 邮箱容量=128/4=32封邮件 + RT_IPC_FLAG_PRIO); // 优先级等待模式 +``` +- **内存池**:`mb_pool[128]` 预分配空间,每封邮件占4字节(指针大小) +- **与前面的信号量类似,邮箱同样有静态和动态两种创建方式,同样是分别使用detach和delete销毁** + +#### 2. 发送线程 +```c +void thread2_entry(void *parameter) { + for(int count=0; count<10; count++){ + // 交替发送两个字符串地址 + if(count % 2) rt_mb_send(&mb, (rt_uint32_t)&mb_str1); + else rt_mb_send(&mb, (rt_uint32_t)&mb_str2); + rt_thread_mdelay(200); // 延时200ms + } + rt_mb_send(&mb, (rt_uint32_t)&mb_str3); // 发送结束标志 +} +``` +- 阻塞发送:若邮箱满,线程挂起直到有空位 +- 无超时机制:直接阻塞等待 + +#### 3.接收线程 +```c +void thread1_entry(void *parameter) { + char *str; + while(1){ + rt_mb_recv(&mb, (rt_ubase_t*)&str, RT_WAITING_FOREVER); // 永久阻塞等待 + if(str == mb_str3) break; // 收到结束标志退出 + rt_thread_mdelay(100); // 处理延时100ms + } + rt_mb_detach(&mb); // 分离邮箱释放内核资源 +} +``` +- `RT_WAITING_FOREVER`:永久阻塞直到收到邮件 +- 可设置超时如`RT_TICK_PER_SECOND`(等待1秒) + +### **线程交互流程** +```mermaid +sequenceDiagram + participant Thread2 as 发送线程 + participant Mailbox as 邮箱(mb) + participant Thread1 as 接收线程 + + Thread2->>Mailbox: rt_mb_send(&mb_str1) + Mailbox->>Thread1: 邮件到达 + Thread1->>Mailbox: rt_mb_recv() 成功 + Thread1-->>Thread1: 打印"I'm a mail!" + + Thread2->>Mailbox: rt_mb_send(&mb_str2) + Mailbox->>Thread1: 邮件到达 + Thread1->>Mailbox: rt_mb_recv() 成功 + Thread1-->>Thread1: 打印"this is another mail!" + + Thread2->>Mailbox: rt_mb_send(&mb_str3) + Mailbox->>Thread1: 邮件到达 + Thread1->>Thread1: 检测到结束标志 + Thread1->>Mailbox: rt_mb_detach() +``` + +- 当多个线程等待邮箱时,优先级高的线程优先获取邮件 +- 邮箱不持有数据:仅传递指针,实际字符串存储在全局内存中 +- 发送阻塞:邮箱满时发送线程挂起(发送速度>接收速度时发生) +- 接收阻塞:邮箱空时接收线程挂起 +- `rt_mb_detach()`:解除邮箱与内核的关联,但**不释放**静态分配的`mb`和`mb_pool` + +### 示例输出 + +``` +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:I'm a mail! +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:this is another mail! +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:I'm a mail! +... +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:over +``` + +## 五、消息队列 + +消息队列是RT-Thread中支持**变长/定长数据块**传递的通信机制 + +- 支持传递任意类型的数据块 +- 提供紧急消息发送机制(插队到队列头部) +- 支持阻塞/非阻塞操作和超时机制 +- 消息内容会被**完整复制**到队列内存池 + +消息队列的结构体如下: +```c +struct rt_messagequeue +{ + struct rt_ipc_object parent; /**< inherit from ipc_object */ + + void *msg_pool; /**< start address of message queue */ + + rt_uint16_t msg_size; /**< message size of each message */ + rt_uint16_t max_msgs; /**< max number of messages */ + + rt_uint16_t entry; /**< index of messages in the queue */ + + void *msg_queue_head; /**< list head */ + void *msg_queue_tail; /**< list tail */ + void *msg_queue_free; /**< pointer indicated the free node of queue */ + + rt_list_t suspend_sender_thread; /**< sender thread suspended on this message queue */ +}; +``` + +### 示例代码 + +```c +/* + * 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 + */ + +/* + * 程序清单:消息队列例程 + * + * 这个程序会创建2个动态线程,一个线程会从消息队列中收取消息;一个线程会定时给消 + * 息队列发送 普通消息和紧急消息。 + */ +#include + +#define THREAD_PRIORITY 25 +#define THREAD_TIMESLICE 5 + +#define RT_VERSION_CHECK(major, minor, revise) ((major * 10000) + (minor * 100) + revise) +/* 消息队列控制块 */ +static struct rt_messagequeue mq; +/* 消息队列中用到的放置消息的内存池 */ +static rt_uint8_t msg_pool[2048]; + +#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) +{ + char buf = 0; + rt_uint8_t cnt = 0; + + while (1) + { + /* 从消息队列中接收消息 */ +#if (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); +} + +#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) +{ + 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++; + /* 延时5ms */ + 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; + } + + rt_thread_init(&thread1, + "thread1", + 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, + "thread2", + 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, msgq sample); + +``` + +#### 1.消息队列初始化 + +```c +/* 消息队列初始化 */ +rt_mq_init(&mq, + "mqt", + &msg_pool[0], // 内存池地址 + 1, // 单条消息大小(字节) + sizeof(msg_pool), // 内存池总大小(2048B) + RT_IPC_FLAG_PRIO); // 优先级等待模式 +``` +- 可存储 `2048 / 1 = 2048` 条消息 + +#### 2. **发送线程(thread2)** +```c +void thread2_entry(void *parameter) { + char buf = 'A'; + for(rt_uint8_t cnt=0; cnt<20; cnt++){ + if(cnt == 8) { + /* 紧急消息(插队到队列头部) */ + rt_mq_urgent(&mq, &buf, 1); + } else { + /* 普通消息(追加到队列尾部) */ + rt_mq_send(&mq, &buf, 1); + } + buf++; + rt_thread_mdelay(5); + } +} +``` +- `rt_mq_urgent()` 将消息插入队列头部(接收方优先获取) +- 发送时完整复制数据到消息池 + +#### 3. **接收线程(thread1)** +```c +void thread1_entry(void *parameter) { + char buf; + rt_uint8_t cnt = 0; + while(1) { + /* 阻塞接收 */ +#if (RTTHREAD_VERSION >= RT_VERSION_CHECK(5,0,1)) + if(rt_mq_recv(&mq, &buf, 1, RT_WAITING_FOREVER) > 0) +#else + if(rt_mq_recv(&mq, &buf, 1, RT_WAITING_FOREVER) == RT_EOK) +#endif + { + if(cnt++ >= 19) break; // 接收20条后退出 + } + rt_thread_mdelay(50); + } + rt_mq_detach(&mq); // 分离队列 +} +``` +- RT-Thread 5.0.1+版本API返回接收字节数 + +--- + +### 消息队列和消息邮箱的一些区别 + +| **特性** | **消息队列 (Message Queue)** | **消息邮箱 (Mailbox)** | +|------------------------|--------------------------------------------|--------------------------------------| +| **数据传递方式** | 传递数据块副本(内存复制) | 传递4字节数据(指针或整数值) | +| **单条数据最大尺寸** | 可配置(例程中1字节) | 固定4字节 | +| **存储机制** | 独立内存池存储数据副本 | 仅存储数据指针/值 | +| **容量** | 由内存池大小和消息长度决定 | 固定邮件槽数量 | +| **特殊功能** | 支持紧急消息(rt_mq_urgent) | 不支持优先级插队 | +| **API返回值** | 5.0.1+版本返回实际接收字节数 | 始终返回RT_EOK | +| **内存开销** | 较高(需预分配数据存储池) | 较低(仅需指针数组) | +| **数据安全性** | 高(数据隔离) | 低(需确保数据生命周期) | +| **典型应用场景** | 传感器数据流、网络数据包 | 事件通知、资源标识传递 | +| **中断服务中可用性** | 仅支持非阻塞发送(rt_mq_send_wait) | 支持非阻塞发送(rt_mb_send_wait) | + +```mermaid +graph LR + A[发送数据] --> B{数据类型} + B -->|数据块| C[消息队列] + B -->|4字节指针/整型| D[消息邮箱] + + C --> E[数据复制到内存池] + D --> F[仅存储地址/值] + + E --> G[接收方获取副本] + F --> H[接收方直接访问原数据] + + G --> I[数据隔离安全] + H --> J[需确保数据有效性] +``` + +**消息队列更适合数据块传输,而消息邮箱更适合轻量级通知。** + +### 示例输出 + +``` +thread2: send message - A +thread1: recv msg from msg queue, the content:A +thread2: send message - B +thread2: send message - C +thread2: send message - D +thread2: send message - E +thread2: send message - F +thread2: send message - G +thread2: send message - H +thread2: send urgent message - I +thread1: recv msg from msg queue, the content:I +thread2: send message - J +thread2: send message - K +... +thread2: send message - S +thread1: recv msg from msg queue, the content:B +thread2: send message - T +message queue stop send, thread2 quit +thread1: recv msg from msg queue, the content:C +thread1: recv msg from msg queue, the content:D +... +thread1: recv msg from msg queue, the content:T +thread1: detach mq +``` + +## 六、信号(Signal) + +信号(Signal)是其进程间通信(IPC)机制之一,用于**异步通知**线程发生某个事件(如中断、错误或自定义事件),信号是轻量级的。 +- **异步性**:发送线程无需等待接收线程响应,信号处理函数在接收线程的上下文中执行(类似于中断上下文)。 +- **API**: + - `rt_signal_install(int signo, void (*handler)(int))`:安装信号处理函数。`signo` 是信号编号,`handler` 是处理函数指针。 + - `rt_signal_unmask(int signo)`:解除信号屏蔽,允许信号被传递和处理。 + - `rt_thread_kill(rt_thread_t thread, int sig)`:向指定线程发送信号。`thread` 是目标线程句柄,`sig` 是信号编号。 + - `rt_signal_mask(int signo)`:屏蔽信号,暂时阻止信号传递。 + +信号有32个: +```c +#define SIGHUP 1 /* hangup */ +#define SIGINT 2 /* interrupt */ +#define SIGQUIT 3 /* quit */ +#define SIGILL 4 /* illegal instruction (not reset when caught) */ +#define SIGTRAP 5 /* trace trap (not reset when caught) */ +#define SIGIOT 6 /* IOT instruction */ +#define SIGABRT 6 /* used by abort, replace SIGIOT in the future */ +#define SIGEMT 7 /* EMT instruction */ +#define SIGFPE 8 /* floating point exception */ +#define SIGKILL 9 /* kill (cannot be caught or ignored) */ +#define SIGBUS 10 /* bus error */ +#define SIGSEGV 11 /* segmentation violation */ +#define SIGSYS 12 /* bad argument to system call */ +#define SIGPIPE 13 /* write on a pipe with no one to read it */ +#define SIGALRM 14 /* alarm clock */ +#define SIGTERM 15 /* software termination signal from kill */ +#define SIGURG 16 /* urgent condition on IO channel */ +#define SIGSTOP 17 /* sendable stop signal not from tty */ +#define SIGTSTP 18 /* stop signal from tty */ +#define SIGCONT 19 /* continue a stopped process */ +#define SIGCHLD 20 /* to parent on child stop or exit */ +#define SIGCLD 20 /* System V name for SIGCHLD */ +#define SIGTTIN 21 /* to readers pgrp upon background tty read */ +#define SIGTTOU 22 /* like TTIN for output if (tp->t_local<OSTOP) */ +#define SIGIO 23 /* input/output possible signal */ +#define SIGPOLL SIGIO /* System V name for SIGIO */ +#define SIGXCPU 24 /* exceeded CPU time limit */ +#define SIGXFSZ 25 /* exceeded file size limit */ +#define SIGVTALRM 26 /* virtual time alarm */ +#define SIGPROF 27 /* profiling time alarm */ +#define SIGWINCH 28 /* window changed */ +#define SIGLOST 29 /* resource lost (eg, record-lock lost) */ +#define SIGUSR1 30 /* user defined signal 1 */ +#define SIGUSR2 31 /* user defined signal 2 */ +#define NSIG 32 /* signal 0 implied */ +``` + +### 示例代码 + +```c +/* + * 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 + */ + +/* + * 程序清单:信号例程 + * + * 这个例子会创建一个线程,线程安装信号,然后给这个线程发送信号。 + * + */ +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; + +/* 线程1的信号处理函数 */ +void thread1_signal_handler(int sig) +{ + rt_kprintf("thread1 received signal %d\n", sig); +} + +/* 线程1的入口函数 */ +static void thread1_entry(void *parameter) +{ + int cnt = 0; + + /* 安装信号 */ + rt_signal_install(SIGUSR1, thread1_signal_handler); + rt_signal_unmask(SIGUSR1); + + /* 运行10次 */ + while (cnt < 10) + { + /* 线程1采用低优先级运行,一直打印计数值 */ + rt_kprintf("thread1 count : %d\n", cnt); + + cnt++; + rt_thread_mdelay(100); + } +} + +/* 信号示例的初始化 */ +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); + + rt_thread_mdelay(300); + + /* 发送信号 SIGUSR1 给线程1 */ + rt_thread_kill(tid1, SIGUSR1); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(signal_sample, signal sample); + +``` + +#### 1. **信号处理函数** +```c +void thread1_signal_handler(int sig) +{ + rt_kprintf("thread1 received signal %d\n", sig); +} +``` +- 定义信号处理函数。当线程1收到信号时,此函数被自动调用。 + - 参数 `sig`:接收到的信号编号。 + - **注意**:信号处理函数执行时,当前线程(线程1)的常规执行被中断,处理完毕后恢复原流程。 + +#### 2. **线程1** +```c +static void thread1_entry(void *parameter) +{ + int cnt = 0; + + /* 安装信号 */ + rt_signal_install(SIGUSR1, thread1_signal_handler); + rt_signal_unmask(SIGUSR1); + + /* 运行10次 */ + while (cnt < 10) + { + rt_kprintf("thread1 count : %d\n", cnt); + cnt++; + rt_thread_mdelay(100); // 延迟100ms + } +} +``` +- `rt_signal_install(SIGUSR1, thread1_signal_handler)`:安装信号处理函数。`SIGUSR1` 是用户自定义信号编号(RT-Thread 中定义为30),关联到 `thread1_signal_handler`。 +- `rt_signal_unmask(SIGUSR1)`:解除 `SIGUSR1` 的屏蔽,允许信号被接收。**如果不调用此函数,信号会被忽略(默认状态)**。 + +- **异步触发**:如果信号在循环中被接收,线程1会立即中断,执行信号处理函数,然后恢复循环。 + +#### 3. **发送信号** +```c +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); // 启动线程1 + + rt_thread_mdelay(300); // 主线程延迟300ms + + /* 发送信号 SIGUSR1 给线程1 */ + rt_thread_kill(tid1, SIGUSR1); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(signal_sample, signal sample); +``` +- 让主线程延迟300ms确保线程1有足够时间启动并安装信号处理函数。 +- `rt_thread_kill(tid1, SIGUSR1)`:向线程1发送信号 `SIGUSR1`。异步触发线程1的信号处理函数。 + +### 执行流程 +1. **初始化**:系统启动后,用户输入 `signal_sample` 命令。 + +2. **线程创建**:`signal_sample` 函数创建并启动线程1。 + +3. **信号安装**:线程1执行 `thread1_entry`,安装 `SIGUSR1` 的处理函数并解除屏蔽。 + +4. **主线程延迟**:主线程延迟300ms,期间线程1开始运行循环(打印计数)。 + +5. **信号发送**:主线程调用 `rt_thread_kill(tid1, SIGUSR1)`,向线程1发送信号。 + +6. **信号处理**: + - 线程1在循环中收到信号,立即中断当前操作。 + - 执行 `thread1_signal_handler`,打印 "thread1 received signal 10"(`SIGUSR1` 编号为10)。 + - 处理完毕后,线程1恢复循环,继续打印计数。 + +### 示例输出 + + ``` + thread1 count : 0 + thread1 count : 1 + thread1 count : 2 + thread1: Signal 30 received + thread1 count : 3 + thread1 count : 4 + thread1 count : 5 + thread1 count : 6 + thread1 count : 7 + thread1 count : 8 + thread1 count : 9 + ``` diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/git.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/git.png" new file mode 100644 index 0000000000000000000000000000000000000000..3f35d2d7abeed15e161dcc2b92d4851533c53aac Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/git.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721212101021-1753104076856-1.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721212101021-1753104076856-1.png" new file mode 100644 index 0000000000000000000000000000000000000000..5f62a199ede312839468e3d7be177ac0dd79b108 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721212101021-1753104076856-1.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721212706368-1753104428395-3.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721212706368-1753104428395-3.png" new file mode 100644 index 0000000000000000000000000000000000000000..918c133a955600bd3c51f311df88fc3742be46a6 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721212706368-1753104428395-3.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721213551215-1753104953615-5.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721213551215-1753104953615-5.png" new file mode 100644 index 0000000000000000000000000000000000000000..b3ed63f433626e196020aa3023a6279e01e8b044 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721213551215-1753104953615-5.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721214431137-1753105472789-7.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721214431137-1753105472789-7.png" new file mode 100644 index 0000000000000000000000000000000000000000..74790df25cbd8ce289ed33e8e13e7a74112f4a23 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721214431137-1753105472789-7.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721215003680-1753105804818-9.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721215003680-1753105804818-9.png" new file mode 100644 index 0000000000000000000000000000000000000000..1b8881b799ecddef006b99c264b886874f504eae Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250721215003680-1753105804818-9.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250725155047099-1753429851297-1.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250725155047099-1753429851297-1.png" new file mode 100644 index 0000000000000000000000000000000000000000..60ee7400d83a278ae591a7108601a2934e8095b2 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/image-20250725155047099-1753429851297-1.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..ccbd8abad5cbcc65034e5055bcddcfba1d7b0434 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/lgvs\347\244\272\344\276\213\345\267\245\347\250\213.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/scans\347\274\226\350\257\221.png" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/scans\347\274\226\350\257\221.png" new file mode 100644 index 0000000000000000000000000000000000000000..b56a44bc6bface47515099ab76e759e90e905a04 Binary files /dev/null and "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/assets/scans\347\274\226\350\257\221.png" differ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\216\257\345\242\203\346\220\255\345\273\272.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\216\257\345\242\203\346\220\255\345\273\272.md" new file mode 100644 index 0000000000000000000000000000000000000000..b6b5e3229f701385caea7e110d6d68df6b613e0c --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\216\257\345\242\203\346\220\255\345\273\272.md" @@ -0,0 +1,38 @@ +# RT-Thread环境搭建(env工具) + +> env下载:https://download-redirect.rt-thread.org/download/env_release/env-windows-v2.0.0.7z +> +> RT-Thread源码下载:[RT-Thread/rt-thread: RT-Thread is an open source IoT Real-Time Operating System (RTOS)](https://github.com/RT-Thread/rt-thread) +> + +下载完成后,进入env工具文件夹,运行env.bat安装env环境 +打开env命令行窗口,右键窗口选择Settings选项,然后选择Integration,将两个指令都注册一下: +![image-20250721212101021](.\assets\image-20250721212101021-1753104076856-1.png) + +然后进入RT-Thread源码,选择bsp(Board Support Package),选择`qemu-vexpress-a9` +在空白处右键,此时如果上一步注册成功会显示`ConEmu Here`的选项,点击即可以此处为cwd运行env命令行 +然后输入`menuconfig`运行,按S保存后退出: +![image-20250721212706368](.\assets\image-20250721212706368-1753104428395-3.png) + +然后运行`scons -j4`编译(-j表示使用的线程数量),运行完成后没有报错且有`rtthread.elf`文件表示编译成功: +![scons编译成功](./assets/scans编译.png) + +然后运行`qemu-nographic.bat`启动无图形化界面的qemu仿真,可以看到RT-Thread OS运行成功: +![image-20250721213551215](./assets/image-20250721213551215-1753104953615-5.png) + +---- + +## 修改源码 + +**scons编译是通过各个文件夹中的`Sconscript`脚本来遍历所需要的源文件,如果新建的文件夹中没有这个脚本,那么scons则会忽略这个文件夹**。新建文件夹,然后添加Sconscript文件,其中的脚本可以从其他文件中直接复制,如果新建的文件内没有嵌套文件夹,那么可以去掉脚本中的for循环部分 +![image-20250721214431137](./assets/image-20250721214431137-1753105472789-7.png) + +---- + +## 运行LVGL示例程序 + +打开env命令行,运行`menuconfig`,然后选择`Hardware Drivers Config -> Onboard Periperal Drivers`,可以看到有`Enable LVGL for LCD`和`Enable LVGL Demo`,全部启用即可: +![image-20250721215003680](./assets/image-20250721215003680-1753105804818-9.png) + +然后运行`pkgs --update`更新一下当前工程的包,运行`scons -j4`编译,编译完成后直接运行`qemu`即可启动示例工程: +![LVGL示例工程](./assets/lgvs示例工程.png) diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\347\254\224\350\256\260.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..5b885f4841a36013c61ea12d23745047c7a074bd --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\347\254\224\350\256\260.md" @@ -0,0 +1,183 @@ +# RT-Thread训练营第五天笔记 + +## [软件包](https://packages.rt-thread.org/) + +RT-Thread的软件包市场目前拥有近800个软件包,生态丰富,在开发项目时可以先看看有没有现成的软件包可以使用。 + +## Scons + +SCons 是一个用 Python 语言编写的开源构建工具,类似于 Make。RT-Thread 引入 SCons 的目的是简化复杂的 Makefile 和 IDE 配置,让开发者更专注于功能开发。 +* **核心文件**: + * `SConstruct`:一个工程通常只有一个,是 SCons 的主入口文件。 + * `SConscript`:通常每个存放源代码的子目录都有一个,用于组织该目录下的源码。 + * `rtconfig.py`:RT-Thread 为每个 BSP 创建的独立配置文件,用于指定编译器、编译选项、链接选项等。 + +#### 常用 SCons 命令 + +在 BSP 工程根目录下的 Env 命令行窗口中执行以下命令: + +* `scons` + * **功能**:编译工程。首次执行为完整编译,后续执行则为增量编译,只编译修改过的文件。 + * **参数**:`scons -s`,编译时不会打印详细的内部命令,输出更简洁。 + +* `scons -c` + * **功能**:清除编译目标。此命令会删除执行 `scons` 时生成的临时文件和目标文件。 + +* `scons --target=XXX` + * **功能**:生成指定 IDE 的工程文件。 + * **示例**: + * `scons --target=mdk5` (生成 Keil MDK5 工程 `project.uvprojx`)。 + * `scons --target=iar` (生成 IAR 工程 `project.eww`)。 + * `scons --target=vsc` (在 `bsp/simulator` 目录下生成 VSCode 工程)。 + * **注意**:每次通过 `menuconfig` 修改了组件配置后,都需要执行此命令重新生成工程文件,以同步源码和配置的变更。 + +* `scons -jN` + * **功能**:开启 N 个线程进行并行编译,可显著加快在多核 CPU 上的编译速度。 + * **示例**:`scons -j4` (使用 4 个线程编译)。 + * **提示**:如果只想查看编译错误或警告,建议不使用 `-j` 参数,以免错误信息被并行输出打乱。 + +* `scons --dist` + * **功能**:构建工程框架。该命令会在 BSP 目录下生成一个 `dist` 目录,其中包含了 RT-Thread 源码和当前 BSP 的相关工程,去除了不相关的 BSP 和 libcpu,方便拷贝和分发。 + +#### 工程配置与管理 + +##### 修改编译器配置 (`rtconfig.py`) + +`rtconfig.py` 文件用于配置编译器类型和编译参数。 + +* **切换编译器**:修改 `CROSS_TOOL` 变量的值,可选值为 `'gcc'`, `'keil'`, `'iar'` 等。 +* **指定编译器路径**:修改 `EXEC_PATH` 变量的值为编译器的安装路径。 +* **注意事项**: + 1. 编译器安装路径中不要包含中文或空格。 + 2. 在 Windows 下修改路径时,需将路径分隔符 `\` 替换为 `/`,或者在字符串前加 `r` (如 `r'D:\Keil_v5'`),或使用双反斜杠 `\\` 进行转义。 + 3. 若要永久修改编译器配置,需要注释掉 `rtconfig.py` 文件中通过 `os.getenv` 获取环境变量的部分,否则 Env 的默认配置会覆盖文件中的修改。 + +##### 添加/管理源码 (`SConscript`) + +通过修改或创建 `SConscript` 文件来管理项目中的源文件。 + +* **批量添加文件**:`applications` 目录下的 `SConscript` 文件默认使用 `src = Glob('*.c')`,会自动将该目录下的所有 `.c` 文件添加到项目中。因此,简单的应用代码可以直接放在此目录下。 +* **创建自定义模块**: + 1. 在 BSP 目录下新建一个模块文件夹(如 `hello`)。 + 2. 在 `hello` 文件夹中创建 `hello.c`, `hello.h` 和一个 `SConscript` 文件。 + 3. `SConscript` 文件内容示例: + ```python + from building import * + + # 获取当前目录 + cwd = GetCurrentDir() + # 定义源文件列表 + src = [] + + # 通过依赖宏决定是否添加源文件 + if GetDepend('RT_USING_HELLO'): + src += ['hello.c'] + + # 定义一个名为 'hello' 的组件(Group) + # CPPPATH 用于添加头文件路径 + group = DefineGroup('hello', src, depend = [''], CPPPATH = [cwd]) + + Return('group') + ``` + 4. 这段脚本的核心是使用 `GetDepend('RT_USING_HELLO')` 判断 `rtconfig.h` 中是否定义了 `RT_USING_HELLO` 宏,如果定义了,才会将 `hello.c` 添加到编译中。 + +## Kconfig + +为了能通过图形化界面控制 `RT_USING_HELLO` 这类宏,需要使用 Kconfig 文件。 + +1. **创建/修改 Kconfig 文件**:在 BSP 根目录的 `Kconfig` 文件中添加模块的配置项。 + ```kconfig + menu "hello module" + config RT_USING_HELLO + bool "Enable hello module" + default y + endmenu + ``` +2. **运行 menuconfig**:在 Env 中执行 `menuconfig` 命令,即可看到新增的 "hello module" 菜单,并可以勾选启用。 +3. **保存与生效**:保存配置后,`RT_USING_HELLO` 宏会自动写入 `rtconfig.h` 文件。此时,之前编写的 `SConscript` 逻辑就会生效。 +4. **最后一步**:务必执行 `scons --target=mdk5` (或其他IDE) 来重新生成工程,使新的文件和配置在 IDE 中可见。 + +#### Kconfig 语法 + +##### **配置项 (Config)** + +* `config`: 定义一个配置项,是 Kconfig 的基本单元。 + * **语法**: `config <配置项名称>`,例如 `config RT_USING_HEAP`。 + +##### **数据类型** + +* `bool`: 布尔类型,在菜单中显示为复选框 `[*]`。 +* `string`: 字符串类型,允许用户输入文本。 +* `int`: 整型。 +* `hex`: 十六进制类型。 + * **语法**: `bool "提示信息"`,例如 `bool "Enable heap"`。 + +##### **属性** + +* `default`: 为配置项设置默认值。 + * **语法**: `default `,例如 `default y` (布尔类型) 或 `default 1024` (整型)。 +* `help` 或 `---help---`: 为配置项提供帮助信息,用户在 `menuconfig` 中选中 `< Help >` 时可以看到。 +* `prompt`: 设置配置项在菜单中显示的提示文本。如果没有 `prompt`,该项就是隐藏的,但仍可通过 `select` 关键字被其他项选中。 + +##### **依赖关系** + +* `depends on <表达式>`: **正向依赖**。表示当前配置项的出现**依赖于**另一个配置项被选中。如果依赖项未满足,当前项在菜单中将不可见或不可选。 + * **示例**: `config RT_USING_TIMER_SOFT` 依赖于 `RT_USING_TIMER`,只有开启了硬件定时器,软定时器选项才可见。 +* `select <配置项>`: **反向依赖**。表示如果选中了当前项,那么被 `select` 的配置项将被**自动选中**,无论它是否有其他依赖。 + * **示例**: Finsh 组件 `select` 了 `RT_USING_DEVICE`,因为 Finsh 的运行需要设备框架的支持。当你开启 Finsh 时,设备框架会自动开启。 + +##### **菜单结构** + +* `menu "菜单名称" ... endmenu`: 定义一个菜单,将一组相关的配置项组织在一起。 +* `choice "选项名称" ... endchoice`: 定义一个单选组,其中的配置项只能选择一个,在菜单中显示为 `(*)`。 +* `comment "注释信息"`: 在菜单中显示一行注释文本,通常用于分组标题或说明。 +* `source "路径/Kconfig"`: 将另一个 Kconfig 文件包含进来,一起解析。 + +#### 添加自定义配置项 + +假设要为一个名为 "my_module" 的自定义模块添加配置开关。 + +1. **创建 Kconfig 文件**:在 `my_module` 的目录下创建一个 `Kconfig` 文件。 +2. **编写配置内容**: + ```kconfig + # file: my_module/Kconfig + + menu "My Awesome Module" # 创建一个专属菜单 + + config RT_USING_MY_MODULE + bool "Enable my awesome module" # bool类型的开关 + default y # 默认开启 + help + This enables the demonstration module, which can print "Hello, RT-Thread!". + + if RT_USING_MY_MODULE # 如果模块被开启 + + config MY_MODULE_BUFFER_SIZE + int "Buffer size for the module" + default 128 + help + Specify the buffer size in bytes. + + config MY_MODULE_GREETING_MSG + string "Greeting message" + default "Hello, RT-Thread!" + + endif # 结束 if 块 + + endmenu # 结束菜单 + ``` +3. **包含到主 Kconfig**:在上一级目录(如 `rt-thread/components/Kconfig`)或 BSP 的 `Kconfig` 文件中,使用 `source` 命令将此文件包含进来。 + ```kconfig + source "my_module/Kconfig" + ``` +4. **运行 menuconfig**:在 Env 中运行 `menuconfig`,就能看到 "My Awesome Module" 菜单及其中的配置项了。 +5. **在代码中使用**:保存配置后,`rtconfig.h` 中会生成对应的宏。在 C 代码中,可以这样使用: + + ```c + #ifdef RT_USING_MY_MODULE + void my_module_init(void) + { + rt_kprintf("%s\n", MY_MODULE_GREETING_MSG); + } + #endif + ``` \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\272\277\347\250\213\347\256\241\347\220\206.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\272\277\347\250\213\347\256\241\347\220\206.md" new file mode 100644 index 0000000000000000000000000000000000000000..70eca5e09305114597a9780a703d15bb5c6d1678 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\347\272\277\347\250\213\347\256\241\347\220\206.md" @@ -0,0 +1,334 @@ +# RT-Thread线程笔记 + +## 一、线程操作函数 + +### rt_thread_create + +动态创建线程,在运行时动态分配TCB和栈空间(从系统堆申请) +使用`rt_thread_delete`来关闭线程 + +```c +rt_thread_t rt_thread_create(const char *name, + void (*entry)(void *parameter), + void *parameter, + rt_uint32_t stack_size, + rt_uint8_t priority, + rt_uint32_t tick); +rt_err_t rt_thread_delete(rt_thread_t thread); +``` + +#### 参数说明 + +- name:线程的名称,RT-Thread内核使用名称来查找线程并获得对应的句柄 +- entry:线程执行的函数 +- parameter:函数参数 +- stack_size:分配给这个线程的栈空间大小(一般往大了分,避免出现难以排查的栈溢出错误) +- priority:线程的优先级,越小越高,最大为32 + - main线程的优先级是10,shell线程是20 +- tick:分配给这个线程的时间片大小(主要用于在同优先级的线程之间实现RR调度) + - 单位是tick,RT-Thread内核默认每秒1000 ticks + +### rt_thread_init + +静态创建线程,动定义线程控制块(TCB)和栈空间,并在编译时预先分配内存(位于RW/ZI段) +使用`rt_thread_detach`来关闭线程 +因为是固定分配的,一般用在高安全性要求的地方 + +```c +rt_err_t rt_thread_init(struct rt_thread *thread, + const char *name, + void (*entry)(void *parameter), + void *parameter, + void *stack_start, + rt_uint32_t stack_size, + rt_uint8_t priority, + rt_uint32_t tick); +rt_err_t rt_thread_detach(rt_thread_t thread); +``` + +#### 参数说明 + +- thread:线程句柄 +- stack_start:线程栈空间的启示地址 + +### 其他函数 + +1. **`rt_thread_t rt_thread_self(void)`** + - 获取当前正在执行的线程句柄。 + - **注意**:不能在中断服务程序(ISR)中调用,且调度器未启动时返回`RT_NULL`。 +2. **`rt_thread_t rt_thread_find(char *name)`** + - 根据线程名称查找并返回对应的线程句柄[1]。 + - 用于通过名称定位特定线程的控制块。 +3. **`rt_err_t rt_thread_startup(rt_thread_t thread)`** + - 启动线程:将线程状态从初始态(`RT_THREAD_INIT`)切换为就绪态(`RT_THREAD_READY`),并加入调度队列。 + - 若启动的线程优先级高于当前线程,则立即触发调度切换。 +4. **`rt_err_t rt_thread_yield(void)`** + - 主动让出CPU:当前线程从就绪队列移到同优先级队列尾部,触发调度器重新选择最高优先级线程运行。 + - 若当前优先级仅有一个线程,则继续执行(无上下文切换)。 +5. **`rt_err_t rt_thread_delay(rt_tick_t tick)`** + - 延时当前线程:使线程挂起指定数量的系统节拍(tick)后自动恢复。 + - **注意**:不可在中断中调用。 +6. **`rt_err_t rt_thread_delay_until(rt_tick_t *tick, rt_tick_t inc_tick)`** + - 绝对延时:基于上次唤醒时间点,精确延时`inc_tick`个节拍[1]。 + - 适用于周期性任务,避免累积误差。 +7. **`rt_err_t rt_thread_mdelay(rt_int32_t ms)`** + - 毫秒级延时:将毫秒时间转换为系统节拍后调用`rt_thread_delay`。 + - 提供更直观的时间单位接口。 +8. **`rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)`** + - 线程控制:支持动态修改线程属性。 + - 常用命令: + - `RT_THREAD_CTRL_CHANGE_PRIORITY`:动态调整优先级 + - `RT_THREAD_CTRL_STARTUP`:等效于`rt_thread_startup` + - `RT_THREAD_CTRL_CLOSE`:等效于`rt_thread_delete`。 +9. **`rt_err_t rt_thread_suspend(rt_thread_t thread)`** + - 挂起指定线程:线程状态变为挂起态(`RT_THREAD_SUSPEND`),移出就绪队列。 + - **注意**:挂起自身时需立即手动调用`rt_schedule()`切换上下文。 +10. **`rt_err_t rt_thread_resume(rt_thread_t thread)`** + - 恢复挂起的线程:线程状态切换为就绪态,重新加入调度队列。 + - 若恢复的线程为当前最高优先级,则立即触发调度切换。 + +## 二、线程创建过程 + +```c +rt_thread_t rt_thread_create(const char *name, + void (*entry)(void *parameter), + void *parameter, + rt_uint32_t stack_size, + rt_uint8_t priority, + rt_uint32_t tick) +{ + // [1] 分配线程控制块(TCB) + struct rt_thread *thread; + void *stack_start; + + // [2] 从对象系统分配线程对象 + thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread, name); + if (thread == RT_NULL) + return RT_NULL; + + // [3] 分配线程栈空间 + stack_start = (void *)RT_KERNEL_MALLOC(stack_size); + if (stack_start == RT_NULL) + { + // [3.1] 栈分配失败时的回滚操作 + rt_object_delete((rt_object_t)thread); + return RT_NULL; + } + + // [4] 核心初始化 + _thread_init(thread, // 线程控制块 + name, // 线程名 + entry, // 入口函数 + parameter, // 入口函数参数 + stack_start, // 栈起始地址 + stack_size, // 栈大小 + priority, // 优先级 + tick); // 时间片 + + return thread; // [5] 返回线程句柄 +} +``` + +### 函数调用栈 +#### 1. `rt_object_allocate()` +```c +rt_object_allocate(RT_Object_Class_Thread, name) +├── rt_object_get_information(RT_Object_Class_Thread) // 获取线程对象容器 +├── object = rt_malloc(information->object_size) // 动态分配TCB内存 +├── rt_memset(object, 0x0, information->object_size) // 初始化对象空间 +├── object->type = type // 设置对象类型 +├── rt_strncpy(object->name, name, RT_NAME_MAX) // 复制名称 +└── rt_list_insert_after(&information->object_list, &(object->list)) // 加入全局对象链表 +``` + +#### 2. `RT_KERNEL_MALLOC()` +```c +#define RT_KERNEL_MALLOC(sz) rt_malloc(sz) // 系统堆内存分配 + +rt_malloc(stack_size) +├── _heap_lock() // 进入临界区 +├── rt_smemalloc(stack_size) // 实际分配函数 +└── _heap_unlock(level) // 离开临界区 +``` + +#### 3. `_thread_init()`(部分) +```c +static rt_err_t _thread_init(struct rt_thread *thread, + const char *name, + void (*entry)(void *parameter), + void *parameter, + void *stack_start, + rt_uint32_t stack_size, + rt_uint8_t priority, + rt_uint32_t tick) +{ + // [1] 初始化线程链表 + rt_list_init(&(thread->tlist)); + + // [2] 设置线程参数 + thread->entry = (void *)entry; + thread->parameter = parameter; + thread->stack_addr = stack_start; + thread->stack_size = stack_size; + + rt_memset(thread->stack_addr, '#', thread->stack_size); + + // [3] 初始化线程栈,这里适配了两种栈增长模式 +#ifdef ARCH_CPU_STACK_GROWS_UPWARD + thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter, + (void *)((char *)thread->stack_addr), + (void *)_thread_exit); +#else + thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter, + (rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)), + (void *)_thread_exit); +#endif /* ARCH_CPU_STACK_GROWS_UPWARD */ + + // [4] 设置调度参数 + RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX); // 最大优先级是32 + thread->current_priority = priority; + thread->init_tick = tick; + thread->remaining_tick = tick; + + // [5] 设置线程状态 + thread->stat = RT_THREAD_INIT; + + // [6] 错误码和清理回调 + thread->error = RT_EOK; + thread->cleanup = RT_NULL; + + // [7] 线程timer的初始化,用于线程自身的计时,和时间片计时是独立的 + rt_timer_init(&(thread->thread_timer), + thread->name, + _thread_timeout, + thread, + 0, + RT_TIMER_FLAG_ONE_SHOT); +} +``` + +#### 4. 硬件栈初始化`rt_hw_stack_init()` +```c +struct exception_stack_frame +{ + rt_uint32_t r0; + rt_uint32_t r1; + rt_uint32_t r2; + rt_uint32_t r3; + rt_uint32_t r12; + rt_uint32_t lr; + rt_uint32_t pc; + rt_uint32_t psr; +}; + +struct stack_frame +{ +#if USE_FPU + rt_uint32_t flag; +#endif /* USE_FPU */ + + /* r4 ~ r11 register */ + rt_uint32_t r4; + rt_uint32_t r5; + rt_uint32_t r6; + rt_uint32_t r7; + rt_uint32_t r8; + rt_uint32_t r9; + rt_uint32_t r10; + rt_uint32_t r11; + // 这里先写r4~r11,再写r0~r3,lr,pc是ARM过程调用标准AAPCS + struct exception_stack_frame exception_stack_frame; +}; +rt_uint8_t *rt_hw_stack_init(void *tentry, + void *parameter, + rt_uint8_t *stack_addr, + void *texit/*线程退出回调*/) +{ + struct stack_frame *stack_frame; + rt_uint8_t *stk; + + // 栈顶对齐(通常8字节对齐) + stk = stack_addr + sizeof(rt_uint32_t); + stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8); + stk -= sizeof(struct stack_frame); + + // 获取栈帧结构 + stack_frame = (struct stack_frame *)stk; + + // 初始化寄存器 + for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++) + { + ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef; + } + + /* 初始化寄存器上下文 */ + stack_frame->exception_stack_frame.r0 = (unsigned long)parameter; /* r0 : argument */ + stack_frame->exception_stack_frame.r1 = 0; /* r1 */ + stack_frame->exception_stack_frame.r2 = 0; /* r2 */ + stack_frame->exception_stack_frame.r3 = 0; /* r3 */ + stack_frame->exception_stack_frame.r12 = 0; /* r12 */ + stack_frame->exception_stack_frame.lr = (unsigned long)texit; /* lr */ + stack_frame->exception_stack_frame.pc = (unsigned long)tentry; /* entry point, pc */ + stack_frame->exception_stack_frame.psr = 0x01000000L; /* PSR */ + + return stk; // 返回当前栈指针 +} +``` + +### 完成创建后的栈空间 +``` +栈顶 (高地址) ++-----------------+ +| 未使用空间 | ++-----------------+ +| stack_frame | <-- thread->sp 指向这里 +| - PSR | +| - PC(entry) | +| - LR(texit) | +| - R12 | +| - R3 | +| - R2 | +| - R1 | +| - R0(parameter)| ++-----------------+ +| 局部变量/函数调用 | ++-----------------+ +| 栈哨兵(可选) | ++-----------------+ <-- stack_start (低地址) +``` + +## 三、代码示例 + +```c +#include + +rt_thread_t tid = RT_NULL; +rt_thread_t tid2 = RT_NULL; +rt_thread_t tid3 = RT_NULL; +void my_thread() { + char* name; + while(1) { + name = rt_thread_self()->name; + rt_kprintf("%s: Hello!\r\n",name); +// rt_thread_delay(1000); + } +} + +int main(void) +{ + // 线程1和线程2优先级轮转,线程3被抢占不会执行 + tid = rt_thread_create("LittlePipu", my_thread, RT_NULL, 1024, 21, 20); + tid2 = rt_thread_create("Cytilopsis", my_thread, RT_NULL, 1024, 21, 20); + tid3 = rt_thread_create("Angelica", my_thread, RT_NULL, 1024, 22, 20); + + if(tid && tid2 && tid3 != RT_NULL) { + rt_thread_startup(tid); + rt_thread_startup(tid2); + rt_thread_startup(tid3); + } + + rt_kprintf("Main: Hello from main!\r\n"); + return RT_EOK; +} +``` + diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\351\251\261\345\212\250\346\241\206\346\236\266.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\351\251\261\345\212\250\346\241\206\346\236\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..a4c7dc8d2c1687b68abb0f72fa7445545b8d68d8 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\351\231\210\345\256\207\351\224\213/\347\254\224\350\256\260/\351\251\261\345\212\250\346\241\206\346\236\266.md" @@ -0,0 +1,279 @@ +# RT-Thread设备驱动 + +RT-Thread的设备管理采用了分层设计:应用层,IO管理层,驱动框架层,驱动层,硬件。通过指针实现类继承和多态。 + +![image-20250725155047099](./assets/image-20250725155047099-1753429851297-1.png) + +使用分层的好处是,在代码移植的时候,因为I/O设备管理层为上层应用提供了统一的、标准化的操作接口,所以只需要更改驱动层和驱动框架层的对接,可以不修改应用层的逻辑。 + +## 设备管理API + +* `rt_device_t rt_device_find(const char *name)` + * **功能**:在系统已注册的设备中,根据指定的设备名称 `name` 查找对应的设备,并返回其设备句柄。 + * **返回值**:成功则返回设备句柄;如果未找到,则返回 `RT_NULL`。 +* `rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)` + * **功能**:打开由设备句柄 `dev` 指定的设备。 + * **参数**:`oflag` 是打开标志,用于指定设备的访问方式,例如: + * `RT_DEVICE_OFLAG_RDONLY`:只读方式打开。 + * `RT_DEVICE_OFLAG_WRONLY`:只写方式打开。 + * `RT_DEVICE_OFLAG_RDWR`:可读可写方式打开。 + * `RT_DEVICE_FLAG_INT_RX`:中断接收模式。 + * `RT_DEVICE_FLAG_DMA_TX`:DMA 发送模式。 + * 会使设备的ref count + 1 +* `rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)` + * **功能**:从设备 `dev` 读取数据到 `buffer` 中。 + * **参数**:`pos` 是读取的偏移量,`size` 是希望读取的数据大小。 + * **返回值**:成功则返回实际读取到的数据大小。 +* `rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)` + * **功能**:将 `buffer` 中的数据写入到设备 `dev`。 + * **参数**:`pos` 是写入的偏移量,`size` 是希望写入的数据大小。 + * **返回值**:成功则返回实际写入的数据大小。 +* `rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)` + * **功能**:向设备 `dev` 发送控制命令 `cmd`,并传递参数 `arg` 。 + * **应用**:常用于配置设备、获取设备状态等特殊操作,例如设置 RTC 时间(`RT_DEVICE_CTRL_RTC_SET_TIME`)或获取块设备信息(`RT_DEVICE_CTRL_BLK_GETGEOME`)。 +* `rt_err_t rt_device_close(rt_device_t dev)` + * **功能**:关闭由设备句柄 `dev` 指定的设备。 + * **返回值**:成功返回 `RT_EOK`;失败则返回错误码。 + * 实际上调用该函数会使对应设备的ref count - 1,当设备的ref count为0时设备才会被关闭。 + +## 创建自定义的设备驱动框架 + +先看一下`rt_device`的结构体内容: + +```c +struct rt_device +{ + struct rt_object parent; /**< inherit from rt_object */ + + enum rt_device_class_type type; /**< device type */ + rt_uint16_t flag; /**< device flag */ + rt_uint16_t open_flag; /**< device open flag */ + + rt_uint8_t ref_count; /**< reference count */ + rt_uint8_t device_id; /**< 0 - 255 */ + + /* device call back */ + rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); + rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); + +#ifdef RT_USING_DEVICE_OPS + const struct rt_device_ops *ops; +#else + /* common device interface */ + 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); +#endif + +#ifdef RT_USING_POSIX_DEVIO + const struct dfs_file_ops *fops; + struct rt_wqueue wait_queue; +#endif + + void *user_data; /**< device private data */ +}; +``` + +可以看到,`rt_device`首先继承自`rt_object`会包含一个object_name,然后是设备类型,flag,引用计数,设备id,以及最重要的设备操作函数指针。在调用`rt_device_read`等API函数的时候,其核心就是调用对应device的对应函数。如果我们要创建一个自己的设备类型,就需要实现这些函数,然后让函数指针指向它。 + +新建一个设备类型,比如`rt_vir_device`,它应该继承`rt_device`。 + +```c +struct rt_vir_ops { // 假设我们定义的这类设备有三个功能,打印,读和写。 + void (*print_info) (struct rt_device* device); + void (*vir_set_val) (struct rt_device* device, rt_uint32_t val); + void (*vir_get_val) (struct rt_device* device, rt_uint32_t* val); +}; + +struct rt_vir_device { + struct rt_device parent; // parent放在第一位,做类型转换的时候可以不计算偏移 + const struct rt_vir_ops *ops; // 对设备的直接操作 +}; +typedef struct rt_vir_device* rt_vir_device_t; +``` + +然后是向IO管理层的注册函数,负责指定设备管理API函数的指向,以及注册设备: + +```c +rt_err_t rt_hw_vir_register(rt_vir_device_t device, const char* name, const struct rt_vir_ops* ops, const void* user_data) { + RT_ASSERT(ops != RT_NULL); + rt_err_t result; + + device->ops = ops; + device->parent.init = _vir_init; + device->parent.open = _vir_open; + device->parent.close = _vir_close; + device->parent.read = _vir_read; + device->parent.write = _vir_write; + device->parent.control = _vir_control; + + result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR); // 向IO管理层注册设备 + return result; +} +``` + +接下来就是根据API函数的签名来具体实现这些指定的函数: + +```c +rt_err_t _vir_init(rt_device_t dev) { + rt_kprintf("Virtual Device Initialized!\n"); + return RT_EOK; +} +rt_err_t _vir_open(rt_device_t dev, rt_uint16_t oflag) { + rt_kprintf("Hello From Virtual Device!\n"); + return RT_EOK; +} +rt_err_t _vir_close(rt_device_t dev){ + rt_kprintf("Virtual Device is closed!\n"); + return RT_EOK; +} +rt_size_t _vir_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_get_val(dev, (rt_uint32_t*)buffer); + return 4; + } + return 0; +} +rt_size_t _vir_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_set_val(dev, *(rt_uint32_t*)buffer); + return 4; + } + return 0; +} +rt_err_t _vir_control(rt_device_t dev, int cmd, void *args) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + rt_kprintf("Virtual Device received cmd %02d\n",cmd); + return RT_EOK; + } + return -RT_ERROR; +} +``` + +可以有一些框架层的特殊API函数,以`rt_vir`开头,比如: + +```c +// 驱动框架层特殊API +rt_err_t rt_vir_read(rt_device_t dev, rt_uint32_t* val) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_get_val(dev, (rt_uint32_t*)val); + return RT_EOK; + } + return -RT_ERROR; +} +rt_err_t rt_vir_write(rt_device_t dev, rt_uint32_t val) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if(vir->ops != RT_NULL) { + vir->ops->vir_set_val(dev, val); + return RT_EOK; + } + return -RT_ERROR; +} +``` + +这些特殊函数不通过IO管理层操控设备,而是通过框架层操控设备。 + +最后来到设备驱动层,这一层就是实现对具体的设备进行操作的函数,也就是ops中的内容,不同的设备可以有不同的ops。先创建一个设备结构体: + +```c +struct vir +{ + struct rt_vir_device parent; + rt_uint32_t val; + char* info; +}; +``` + +它继承新建的设备类型`rt_vir_device`,有自己的私有成员,这个结构体就对应一个具体的VIR类设备。同样可以创建几个有不同的私有成员的结构体,对应不同型号的VIR类设备。 + +然后是设备的驱动编写: + +```c +struct vir vir; // 一个未初始化的VIR设备 +// 编写设备驱动 +void print_info(struct rt_device* device) { + struct vir* dev = (struct vir*)device; + if(dev->info)rt_kprintf("VIR Info: %s\n",dev->info); +} +void vir_set_val(struct rt_device* device, rt_uint32_t val) { + struct vir* dev = (struct vir*)device; + if(dev)dev->val = val; +} +void vir_get_val(struct rt_device* device, rt_uint32_t* val) { + struct vir* dev = (struct vir*)device; + if(dev)*val = dev->val; +} +// 将驱动函数封装为ops +struct rt_vir_ops ops = { + print_info, + vir_set_val, + vir_get_val +}; +static int vir_init() { // 对设备的初始化 + vir.val = 0; + vir.info = "This is a Virtual Device!"; + rt_hw_vir_register(&vir.parent, "VIRPipu!", &ops, &vir.info); // 注册该VIR设备,名字是"VIRPipu!" + return 0; +} +``` + +RT-Thread的名称最大字符数是8,所以如果注册的设备名称超过8个字节会导致注册失败。虽然在`rtconfig.h`中有`RT_NAME_MAX 8`的字段,但是把它改成16并没有起作用,还是会注册失败。 + +最后通过自动初始化机制实现自动运行这个init函数: + +```c +INIT_APP_EXPORT(vir_init); +``` + +这个自动初始化宏的工作原理如下:(具体参考[RT-Thread文档](https://www.rt-thread.org/document/api/group___system_init.html)) + +1. **注册函数**:编译器在编译时,会创建一个指向 `vir_init` 函数的指针,并将其放入 `.rti_fn.6` 数据段中。 +2. **链接**:链接器在链接所有目标文件时,会将所有源文件中位于 `.rti_fn.1`, `.rti_fn.2`, ..., `.rti_fn.6` 等段的数据收集起来,并按照数字顺序将这些段依次排列在内存中。这样就形成了一个按初始化级别排序的函数指针数组。 +3. **执行**:在系统启动过程中,会调用 `rt_components_init()` 函数 。`rt_components_init()` 函数通过链接器生成的起始和结束地址(例如 `__rt_init_rti_board_end` 和 `__rt_init_rti_end`)来确定初始化函数指针表的范围。该函数会遍历这个指针表,依次调用从 `INIT_PREV_EXPORT`("2") 到 `INIT_APP_EXPORT`("6") 的所有已注册的初始化函数。 + +这样就实现了一个简单的自定义设备,从驱动到驱动框架。可以在main函数中通过IO管理API函数来找到这个设备并进行操作: + +```c +int main(void) +{ + rt_device_t dev = rt_device_find("VIRPipu!"); + if(dev == RT_NULL) { + rt_kprintf("Failed to find VIR Device\n"); + return -RT_ERROR; + } + rt_device_open(dev, RT_DEVICE_FLAG_RDWR); // 要先打开设备才能使用 + + rt_uint32_t buffer = 721; + rt_uint32_t ret = 0; + rt_device_write(dev, 0, &buffer, 486); // sizes字段没有使用,所以可以随便写 + rt_device_read(dev, 0, &ret, 486); + rt_kprintf("You have %04d in VIR Device!\n",ret); + + rt_device_close(dev); + return 0; +} +``` + +最后运行结果如下: + +``` + + \ | / +- RT - Thread Operating System + / | \ 4.1.0 build Jul 25 2025 20:37:43 + 2006 - 2022 Copyright by RT-Thread team +[1] I/sal.skt: Socket Abstraction Layer initialize success. +Virtual Device Initialized! +Hello From Virtual Device! +You have 0721 in VIR Device! +Virtual Device is closed! +msh /> +``` +