STM32F1与STM32CubeIDE编程实例-ThreadX中的消息队列(Message Queue)服务 ThreadX中的消息队列(Message Queue)服务 文章目录
1、消息队列(Message Queue)介绍 消息队列是 ThreadX 中线程间通信的主要方式。 一条或多条消息可以驻留在消息队列中,该队列通常遵循 FIFO 规则。 只能容纳一条消息的消息队列通常称为**邮箱(Mailbox)**。
tx_queue_send 服务将消息放在队列的后面,而 tx_queue_receive 服务从队列的前面删除消息。 线程在等待来自空队列的消息时挂起。 在这种情况下,ThreadX 将发送到队列的下一条消息直接放入线程的目标区域,从而完全绕过队列。如下图所示:
每个消息队列都是公共资源。 ThreadX 对消息队列的使用方式没有任何限制。
应用程序可以在初始化期间或运行时创建消息队列。 应用程序可以使用的消息队列数量没有限制。
可以创建消息队列以支持各种消息大小。 可用的消息大小为 1、2、4、8 和 16 个 32 位字。 创建队列时指定消息大小。 如果应用程序消息超过 16 个字,必须通过指针发送消息。 为此,创建一个消息大小为一个字(足以容纳一个指针)的队列,然后发送和接收消息指针而不是整个消息。
队列可以容纳的消息数量取决于其消息大小和创建期间提供的内存区域的大小。 要计算队列的总消息容量,请将每条消息中的字节数除以提供的内存区域中的总字节数。
例如,如果创建一个支持一个 32 位字(四个字节)的消息大小的消息队列,并且该队列有 100 字节的可用内存区域,则其容量为 25 条消息。
用于缓冲消息的内存区域是在队列创建期间指定的。 它可以位于目标地址空间中的任何位置。 这是一个重要的特性,因为它为应用程序提供了相当大的灵活性。 例如,应用程序可能会在高速 RAM 中定位一个非常重要的队列的内存区域以提高性能。
应用程序线程可以在尝试从队列发送或接收消息时挂起。 通常,线程挂起涉及等待来自空队列的消息。 但是,线程也可以暂停尝试将消息发送到完整队列。
挂起条件解决后,请求完成,等待线程恢复。 如果多个线程在同一个队列上挂起,它们会按照它们在挂起列表(通常是 FIFO)中出现的顺序恢复。 但是,应用程序可以通过在解除线程挂起的队列服务之前调用 tx_queue_prioritize 服务来使更高优先级的线程首先恢复。 队列优先级服务将最高优先级的线程放在挂起列表的最前面,同时让所有其他挂起的线程保持相同的 FIFO 顺序。
队列挂起也可能超时; 本质上,超时指定线程将保持挂起的最大计时器滴答数。 如果发生超时,则恢复线程并且服务返回相应的错误代码。
2、队列控制块(Message Queue Control Block) 消息队列控制块包含消息大小、消息总容量、队列中当前消息数、可用队列存储空间以及在此消息队列上挂起的线程数等信息。用于管理运行时消息队列的状态。
消息队列控制块可以位于内存中的任何位置,但通常通过在任何函数范围之外定义控制块来使控制块成为全局结构。 当使用 TX_QUEUE 数据类型声明消息队列时,将创建消息队列组控制块。
消息队列控制块由TX_QUEUE数据结构声明,其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 /\* Define the queue structure utilized by the application. \*/ typedef struct TX\_QUEUE\_STRUCT { /\* Define the queue ID used for error checking. \*/ ULONG tx_queue_id; /\* Define the queue's name. \*/ CHAR \*tx_queue_name; /\* Define the message size that was specified in queue creation. \*/ UINT tx_queue_message_size; /\* Define the total number of messages in the queue. \*/ UINT tx_queue_capacity; /\* Define the current number of messages enqueued and the available queue storage space. \*/ UINT tx_queue_enqueued; UINT tx_queue_available_storage; /\* Define pointers that represent the start and end for the queue's message area. \*/ ULONG \*tx_queue_start; ULONG \*tx_queue_end; /\* Define the queue read and write pointers. Send requests use the write pointer while receive requests use the read pointer. \*/ ULONG \*tx_queue_read; ULONG \*tx_queue_write; /\* Define the queue suspension list head along with a count of how many threads are suspended. \*/ struct TX\_THREAD\_STRUCT \*tx_queue_suspension_list; UINT tx_queue_suspended_count; /\* Define the created list next and previous pointers. \*/ struct TX\_QUEUE\_STRUCT \*tx_queue_created_next, \*tx_queue_created_previous; #ifdef TX\_QUEUE\_ENABLE\_PERFORMANCE\_INFO /\* Define the number of messages sent to this queue. \*/ ULONG tx_queue_performance_messages_sent_count; /\* Define the number of messages received from this queue. \*/ ULONG tx_queue_performance_messages_received_count; /\* Define the number of empty suspensions on this queue. \*/ ULONG tx_queue_performance_empty_suspension_count; /\* Define the number of full suspensions on this queue. \*/ ULONG tx_queue_performance_full_suspension_count; /\* Define the number of full non-suspensions on this queue. These messages are rejected with an appropriate error code. \*/ ULONG tx_queue_performance_full_error_count; /\* Define the number of queue timeouts. \*/ ULONG tx_queue_performance_timeout_count; #endif #ifndef TX\_DISABLE\_NOTIFY\_CALLBACKS /\* Define the application callback routine used to notify the application when the a message is sent to the queue. \*/ VOID (\*tx_queue_send_notify)(struct TX\_QUEUE\_STRUCT \*queue_ptr); #endif /\* Define the port extension in the queue control block. This is typically defined to whitespace in tx\_port.h. \*/ TX_QUEUE_EXTENSION } TX_QUEUE;
3、消息队列服务 3.1 创建消息队列 消息队列使用 TX_QUEUE 数据类型声明,并使用 tx_queue_create 服务定义。 定义消息队列时,必须指定其控制块、消息队列的名称、消息大小、队列的起始地址以及消息队列可用的总字节数。其原型如下:
UINT tx_queue_create(TX_QUEUE *queue_ptr, CHAR *name_ptr,UINT message_size, VOID *queue_start,ULONG queue_size);
其中:
queue_ptr :指向队列控制块的指针
name_ptr :指向队列名称的指针
message_size :队列消息大小
queue_start :指向队列内存基地址的指针
queue_size :消息队列大小。
当创建成功后,函数返回TX_SUCCESS。
简单示例如下:
1 2 3 4 5 TX_QUEUE my_queue; UINT status; status = tx\_queue\_create(&my_queue, "my\_queue\_name",TX_4_ULONG, (VOID \*) 0x300000, 2000);
如果变量status的值等于 TX_SUCCESS,my_queue 包含用于存储 125 条消息的空间:(2000 字节/每条消息 16 字节)。
注意:队列的内存地址0x300000可以为高速内存位置。如果没有外部调整RAM,使用内部RAM,需要指定。比如,从字节内存池中分配,指向静态数组。
3.2 删除队列 tx_queue_delete 服务删除消息队列。 挂起等待来自此队列的消息的所有线程都将恢复并接收 TX_DELETED 返回状态。 不要尝试使用已删除的消息队列。 此外,请确保您管理与已删除队列关联的内存区域。其原型如下:
UINT tx_queue_delete(TX_QUEUE *queue_ptr);
当删除成功,则函数返回TX_SUCCESS。
简单示例如下:
1 2 3 4 5 6 7 TX_QUEUE my_queue; UINT status; ... status = tx\_queue\_delete(&my_queue);
如果变量status的值为TX_SUCCESS,则表示队列删除成功。
3.3 发送消息 tx_queue_send 服务将消息发送到指定的消息队列。 该服务将要发送的消息从源指针指定的内存区域复制到队列的后面。其原型如下:
UINT tx_queue_send(TX_QUEUE *queue_ptr, VOID *source_ptr, ULONG wait_option);
其中:
queue_ptr :指向消息队列控制块的指针
source_ptr :指向消息源的指针
wait_option :等待方式。
如果消息发送成功,则函数返回TX_SUCCESS。
简单示例如下:
1 2 3 4 5 6 7 8 TX_QUEUE my_queue; UINT status; ULONG my_message[4]; ... status = tx\_queue\_send(&my_queue, my_message, TX_NO_WAIT);
向“my_queue”发送消息。 无论成功与否,立即返回。 此等待选项用于来自初始化、计时器和 ISR 的调用。
如果status等于 TX_SUCCESS,则消息已发送到队列。
3.4 接收消息 tx_queue_receive 服务从消息队列中检索消息。 该服务将检索到的消息从队列的前面复制到目标指针指定的内存区域。 然后从队列中删除该消息。 指定的目标内存区域必须足够大以容纳消息; 即,destination_ptr 指向的目标必须至少与该队列定义的消息大小一样大。 否则,在目的消息内存区域中会发生内存损坏。其原型如下:
UINT tx_queue_receive(TX_QUEUE *queue_ptr,VOID *destination_ptr, ULONG wait_option);
其中:
queue_ptr :指向消息控制块的指针
destination_ptr :指向目标消息地址的指针
wait_option :等待挂起选项。
如果接收信息成功,则返回TX_SUCCESS。
简单示例如下:
1 2 3 4 5 6 7 8 TX_QUEUE my_queue; UINT status; ULONG my_message[4]; … status = tx\_queue\_receive(&my_queue, my_message,TX_WAIT_FOREVER);
从“my_queue”中检索消息。 如果队列为空,则挂起直到出现消息。 请注意,这种挂起只能从应用程序线程中进行。
如果status的值为TX_SUCCESS,则表示信息检索成功。
3.5 刷新消息队列的内容 tx_queue_flush 服务删除存储在消息队列中的所有消息。 此外,如果队列已满并且有线程因为试图向该队列发送消息而挂起,则这些挂起线程的所有消息都将被丢弃,并且每个挂起的线程都以成功返回状态恢复。 如果队列为空,则此服务不执行任何操作。其原型如下:
UINT tx_queue_flush(TX_QUEUE *queue_ptr);
简单示例如下:
1 2 3 4 5 6 7 TX_QUEUE my_queue; UINT status; ... status = tx\_queue\_flush(&my_queue);
示例代码删除消息队列中的所有消息。假设队列已经通过调用 tx_queue_create 创建。
如果status的值为TX_SUCCESS,则表示操作成功。
3.6 将消息发送到消息队列的前端 通常,消息被发送到队列的尾部,但是应用程序可以使用 tx_queue_front_send 服务将消息发送到消息队列的前端位置。该服务将消息从指定的内存区域复制到队列的前端源指针。这对一些有紧急重要的消息处理有非常大的作用。其原型如下:
UINT tx_queue_front_send(TX_QUEUE *queue_ptr, VOID *source_ptr, ULONG wait_option);
参数与tx_queue_send的相同,在这里就不再描述。简单示例如下:
1 2 3 4 5 6 7 8 TX_QUEUE my_queue; UINT status; ULONG my_message[TX_4_ULONG]; ... status = tx\_queue\_front\_send(&my_queue, my_message,TX_NO_WAIT);
当status的值为TX_SUCCESS时,表示消息发送成功。
3.7 优先考虑消息队列挂起列表 tx_queue_prioritize 服务将挂起的消息队列的最高优先级线程放在挂起列表的前面。 这适用于等待从空队列接收消息的线程,或者适用于等待将消息发送到满队列的线程。其原型如下:
UINT tx_queue_prioritize(TX_QUEUE *queue_ptr);
简单示例如下:
1 2 3 4 5 TX_QUEUE my_queue; UINT status; status = tx\_queue\_prioritize(&my_queue);
在示例中,根据队列状态,调用tx_queue_prioritize服务确保最高优先级线程将接收放置在此队列中的下一条消息,或者将下一条消息发送到队列。如果 status 等于 TX_SUCCESS,则最高优先级的挂起线程位于列表的前面。 如果挂起的线程正在等待接收消息,则对该队列进行的下一次 tx_queue_send 或 tx_queue_front_send 调用将唤醒该线程。 如果挂起的线程正在等待发送消息,则下一次 tx_queue_receive 调用将唤醒该线程。
3.8 消息队列通知 tx_queue_send_notify 服务注册一个通知回调函数,每当有消息发送到指定队列时就会调用该函数。 通知回调的处理由应用程序定义。 这是一个事件链示例,其中通知服务用于将各种同步事件链接在一起。 当单个线程必须处理多个同步事件时,这通常很有用。其原型如下:
UINT tx_queue_send_notify(TX_QUEUE *queue_ptr, VOID (*queue_send_notify)(TX_QUEUE *));
3.9 消息队列信息查询 有三个服务函数用于检索消息队列信息:
tx_queue_info_get :从消息队列控制块中检索信息子集。此信息提供了特定时刻(即调用服务时)的“快照(Snapshot)”
两个用于性能统计:
tx_queue_performance_info_get :检索指定消息队列的信息。
tx_queue_performance_system_info_get :检索所有消息队列的信息。
它们的原型分别如下:
UINT tx_queue_info_get(TX_QUEUE *queue_ptr, CHAR **name, ULONG *enqueued, ULONG *available_storage, TX_THREAD **first_suspended, ULONG *suspended_count, TX_QUEUE **next_queue);
UINT tx_queue_performance_info_get(TX_QUEUE *queue_ptr, ULONG *messages_sent, ULONG *messages_received, ULONG *empty_suspensions, ULONG *full_suspensions, ULONG *full_errors, ULONG *timeouts);
UINT tx_queue_performance_system_info_get(ULONG *messages_sent, ULONG *messages_received, ULONG *empty_suspensions, ULONG *full_suspensions, ULONG *full_errors, ULONG *timeouts);
简单示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 TX_QUEUE my_queue; CHAR \*name; ULONG enqueued; TX_THREAD \*first_suspended; ULONG suspended_count; ULONG available_storage; TX_QUEUE \*next_queue; UINT status; ... status = tx\_queue\_info\_get(&my_queue, &name, &enqueued, &available_storage, &first_suspended, &suspended_count, &next_queue);
如果status的值为TX_SUCCESS,则表示信息检索成功。
4、完整示例 在本节中,将实现创建一个消息队列。一个生产者线程,两个消费者线程。其中生产者线程需要100ms产生一个消息;消费者线程每100ms获取一次消息。
4.1 应用程序 1)基本定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /\* \* app.h \* \* Created on: May 3, 2022 \* Author: jenson \*/ #ifndef \_\_APP\_H\_\_ #define \_\_APP\_H\_\_ #include "tx\_api.h" // 应用程序消息队列 extern TX_QUEUE app_message_queue;; void app\_start(void); uint16\_t create\_task\_stack(void\*\* statck,uint32\_t size); #endif /\* \_\_APP\_H\_\_ \*/
2)定义实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 /\* \* app.c \* \* Created on: May 3, 2022 \* Author: jenson \*/ #include <stdio.h> #include "main.h" #include "app.h" #include "tx\_api.h" #include "tx\_thread.h" #include <stdbool.h> #include "tasks/consumer\_task.h" #include "tasks/producer\_task.h" // 消息队列 TX_QUEUE app_message_queue; // 消息队列内存 uint8\_t \* __message_queue_memory = NULL; // 10k应用程序内存 #define APP\_MEMORY\_SIZE 1024 \* 10 // 字节内存句柄 static TX_BYTE_POOL __s_app_statc_pool; static uint8\_t __app_memory_area[APP_MEMORY_SIZE]; // 创建字节内存池 uint16\_t create\_app\_byte\_pool() { UINT status; // 创建字节内存池 status = tx\_byte\_pool\_create(&__s_app_statc_pool, "app\_byte\_pool", __app_memory_area, APP_MEMORY_SIZE); return status; } // 创建字节栈 uint16\_t create\_task\_stack(void \*\*statck, uint32\_t size) { UINT status; // 分配字节内存 status = tx\_byte\_allocate(&__s_app_statc_pool, statck, size, TX_NO_WAIT); return status; } void tx\_application\_define(void \*first_unused_memory) { uint16\_t status = create\_app\_byte\_pool(); if (status != TX_SUCCESS) { printf("create app memory byte pool failed:code = %d\r\n", status); while (true) ; } // 分配消息队列内存 status = create\_task\_stack(&__message_queue_memory,1024); if(status != TX_SUCCESS){ printf("allocate message queue stack failed:code = %d\r\n",status); while(1); } // 创建消息队列 status = tx\_queue\_create(&app_message_queue,"app\_message\_queue",TX_4_ULONG,__message_queue_memory,1024); if (status != TX_SUCCESS) { printf("create message queue failed:code = %d\r\n", status); while (true) ; } create\_producer\_task(); create\_consumer\_task(); } void app\_start(void) { // 启用内核调度 tx\_kernel\_enter(); }
4.2 生产者线程 1)基本定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /\* \* producer\_task.h \* \* Created on: May 3, 2022 \* Author: jenson \*/ #ifndef \_\_PRODUCER\_TASK\_H\_\_ #define \_\_PRODUCER\_TASK\_H\_\_ void create\_producer\_task(void); #endif /\* \_\_PRODUCER\_TASK\_H\_\_ \*/
2)定义实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 /\* \* producer\_task.c \* \* Created on: May 3, 2022 \* Author: jenson \*/ #include "tx\_api.h" #include "tx\_thread.h" #include <stdio.h> #include <tasks/producer\_task.h> #include "app.h" #define PRODUCER\_THREAD\_STACK\_SIZE 512 TX_THREAD producer_thread_handle; uint8\_t \* producer_thread_stack = NULL; void producer\_task\_entry(void \*params) { printf("[task]producer started\r\n"); uint32\_t counter = 0; for (;;) { counter++; ULONG message = counter; tx\_queue\_send(&app_message_queue,&message,TX_NO_WAIT); printf("producer sent:%d\r\n",counter); tx\_thread\_sleep(100); } } void create\_producer\_task(void){ uint16\_t status = create\_task\_stack(&producer_thread_stack, PRODUCER_THREAD_STACK_SIZE); if(status != TX_SUCCESS){ printf("cannot create producer task stack\r\n"); return; } tx\_thread\_create(/\*创建线程\*/ &producer_thread_handle, /\*线程句柄\*/ "producer\_task", /\*线程名称\*/ producer_task_entry, /\*线程执行函数\*/ NULL, /\*线程函数\*/ producer_thread_stack, /\*线程堆栈入口\*/ PRODUCER_THREAD_STACK_SIZE, /\*线程堆栈大小\*/ 2, /\*优先级\*/ 2, /\*抢占阈值\*/ TX_NO_TIME_SLICE, /\*线程时间切片类型\*/ TX_AUTO_START /\*线程启动类型:自动启动\*/ ); printf("created producer task\r\n"); }
4.3 消费者线程 1)基本定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /\* \* consumer\_task.h \* \* Created on: May 3, 2022 \* Author: jenson \*/ #ifndef \_\_CONSUMER\_TASK\_H\_\_ #define \_\_CONSUMER\_TASK\_H\_\_ void create\_consumer\_task(void); #endif /\* \_\_CONSUMER\_TASK\_H\_\_ \*/
2)定义实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 /\* \* consumer\_task.c \* \* Created on: May 3, 2022 \* Author: jenson \*/ #include "tx\_api.h" #include "tx\_thread.h" #include <stdio.h> #include <tasks/consumer\_task.h> #include "app.h" #define CONSUMER\_THREAD\_STACK\_SIZE 512 TX_THREAD consumer1_thread_handle; uint8\_t \*consumer1_thread_stack = NULL; TX_THREAD consumer2_thread_handle; uint8\_t \*consumer2_thread_stack = NULL; void consumer1\_task\_entry(void \*params) { printf("[task]consumer 1 started\r\n"); ULONG message = 0; for (;;) { tx\_queue\_receive(&app_message_queue,&message,TX_WAIT_FOREVER); printf("consumer 1 received:msg = %d\r\n",message); tx\_thread\_sleep(100); } } void consumer2\_task\_entry(void \*params) { printf("[task]consumer 2 started\r\n"); ULONG message = 0; for (;;) { tx\_queue\_receive(&app_message_queue,&message,TX_WAIT_FOREVER); printf("consumer 2 received:msg = %d\r\n",message); tx\_thread\_sleep(100); } } void create\_consumer\_task(void) { uint16\_t status = create\_task\_stack(&consumer1_thread_stack, CONSUMER_THREAD_STACK_SIZE); if (status != TX_SUCCESS) { printf("cannot create consumer 1 task stack\r\n"); return; } status = create\_task\_stack(&consumer2_thread_stack, CONSUMER_THREAD_STACK_SIZE); if (status != TX_SUCCESS) { printf("cannot create consumer 2 task stack\r\n"); return; } tx\_thread\_create(/\*创建线程\*/ &consumer1_thread_handle, /\*线程句柄\*/ "consumer1\_task", /\*线程名称\*/ consumer1_task_entry, /\*线程执行函数\*/ NULL, /\*线程参数\*/ consumer1_thread_stack, /\*线程堆栈入口\*/ CONSUMER_THREAD_STACK_SIZE, /\*线程堆栈大小\*/ 3, /\*优先级\*/ 3, /\*抢占阈值\*/ TX_NO_TIME_SLICE, /\*线程时间切片类型\*/ TX_AUTO_START /\*线程启动类型:自动启动\*/ ); tx\_thread\_create(/\*创建线程\*/ &consumer2_thread_handle, /\*线程句柄\*/ "consumer2\_task", /\*线程名称\*/ consumer2_task_entry, /\*线程执行函数\*/ NULL, /\*线程参数\*/ consumer2_thread_stack, /\*线程堆栈入口\*/ CONSUMER_THREAD_STACK_SIZE, /\*线程堆栈大小\*/ 3, /\*优先级\*/ 3, /\*抢占阈值\*/ TX_NO_TIME_SLICE, /\*线程时间切片类型\*/ TX_AUTO_START /\*线程启动类型:自动启动\*/ ); printf("created consumer task\r\n"); }
4.4 主程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 /\* Private includes ----------------------------------------------------------\*/ /\* USER CODE BEGIN Includes \*/ #include "app.h" #include <stdio.h> /\* USER CODE END Includes \*/ int main(void) { /\* USER CODE BEGIN 1 \*/ /\* USER CODE END 1 \*/ /\* MCU Configuration--------------------------------------------------------\*/ /\* Reset of all peripherals, Initializes the Flash interface and the Systick. \*/ HAL\_Init(); /\* USER CODE BEGIN Init \*/ /\* USER CODE END Init \*/ /\* Configure the system clock \*/ SystemClock\_Config(); /\* USER CODE BEGIN SysInit \*/ /\* USER CODE END SysInit \*/ /\* Initialize all configured peripherals \*/ MX\_GPIO\_Init(); MX\_USART1\_UART\_Init(); /\* USER CODE BEGIN 2 \*/ printf("\*\*\*\*STM32CubeIDE:ThreadX Demo\*\*\*\*\r\n"); app\_start(); /\* USER CODE END 2 \*/ /\* Infinite loop \*/ /\* USER CODE BEGIN WHILE \*/ while (1) { /\* USER CODE END WHILE \*/ /\* USER CODE BEGIN 3 \*/ } /\* USER CODE END 3 \*/ }
程序运行结果:
文章来源: https://iotsmart.blog.csdn.net/article/details/125155477